From d130f7fca2872ac07401f650c3fe23e79b655286 Mon Sep 17 00:00:00 2001 From: Charlotte Date: Sat, 12 Aug 2023 11:59:39 +1000 Subject: [PATCH 01/70] tests: use /usr/bin/env for bash. --- backends/btor/test_cells.sh | 2 +- backends/firrtl/test.sh | 2 +- backends/simplec/test00.sh | 2 +- backends/smt2/test_cells.sh | 2 +- backends/smv/test_cells.sh | 2 +- libs/bigint/run-testsuite | 2 +- techlibs/ice40/tests/test_bram.sh | 2 +- techlibs/ice40/tests/test_dsp_map.sh | 2 +- techlibs/ice40/tests/test_dsp_model.sh | 2 +- techlibs/ice40/tests/test_ffs.sh | 2 +- techlibs/xilinx/tests/bram1.sh | 2 +- techlibs/xilinx/tests/bram2.sh | 2 +- techlibs/xilinx/tests/test_dsp48_model.sh | 2 +- techlibs/xilinx/tests/test_dsp48a1_model.sh | 2 +- techlibs/xilinx/tests/test_dsp_model.sh | 2 +- tests/aiger/run-test.sh | 2 +- tests/arch/run-test.sh | 2 +- tests/asicworld/run-test.sh | 2 +- tests/blif/run-test.sh | 2 +- tests/bram/run-single.sh | 2 +- tests/bram/run-test.sh | 2 +- tests/fmt/run-test.sh | 2 +- tests/fsm/run-test.sh | 2 +- tests/hana/run-test.sh | 2 +- tests/liberty/run-test.sh | 2 +- tests/lut/run-test.sh | 2 +- tests/memfile/run-test.sh | 2 +- tests/memlib/run-test.sh | 2 +- tests/memories/run-test.sh | 2 +- tests/opt/run-test.sh | 2 +- tests/opt_share/run-test.sh | 2 +- tests/proc/run-test.sh | 2 +- tests/realmath/run-test.sh | 2 +- tests/rpc/run-test.sh | 2 +- tests/select/run-test.sh | 2 +- tests/share/run-test.sh | 2 +- tests/simple/run-test.sh | 2 +- tests/simple_abc9/run-test.sh | 2 +- tests/smv/run-single.sh | 2 +- tests/smv/run-test.sh | 2 +- tests/sva/runtest.sh | 2 +- tests/svinterfaces/run_simple.sh | 2 +- tests/svinterfaces/runone.sh | 2 +- tests/techmap/mem_simple_4x1_runtest.sh | 2 +- tests/various/async.sh | 2 +- tests/various/chparam.sh | 2 +- tests/various/logger_fail.sh | 2 +- tests/various/smtlib2_module.sh | 2 +- tests/various/sv_implicit_ports.sh | 2 +- tests/various/svalways.sh | 2 +- tests/verilog/dynamic_range_lhs.sh | 2 +- tests/vloghtb/run-test.sh | 2 +- tests/vloghtb/test_febe.sh | 2 +- tests/vloghtb/test_mapopt.sh | 2 +- tests/vloghtb/test_share.sh | 2 +- tests/xprop/run-test.sh | 2 +- 56 files changed, 56 insertions(+), 56 deletions(-) diff --git a/backends/btor/test_cells.sh b/backends/btor/test_cells.sh index 0a011932d22..f8bd797825e 100755 --- a/backends/btor/test_cells.sh +++ b/backends/btor/test_cells.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex diff --git a/backends/firrtl/test.sh b/backends/firrtl/test.sh index fe7e3a32912..dd675e91712 100644 --- a/backends/firrtl/test.sh +++ b/backends/firrtl/test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex cd ../../ diff --git a/backends/simplec/test00.sh b/backends/simplec/test00.sh index ede75727378..9895a97e46b 100644 --- a/backends/simplec/test00.sh +++ b/backends/simplec/test00.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex ../../yosys -p 'synth -top test; write_simplec -verbose -i8 test00_uut.c' test00_uut.v clang -o test00_tb test00_tb.c diff --git a/backends/smt2/test_cells.sh b/backends/smt2/test_cells.sh index 34adb7af3c1..3f84d65a21a 100644 --- a/backends/smt2/test_cells.sh +++ b/backends/smt2/test_cells.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex diff --git a/backends/smv/test_cells.sh b/backends/smv/test_cells.sh index 145b9c33b92..1347b70446b 100644 --- a/backends/smv/test_cells.sh +++ b/backends/smv/test_cells.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex diff --git a/libs/bigint/run-testsuite b/libs/bigint/run-testsuite index ff737291641..8436ebda057 100755 --- a/libs/bigint/run-testsuite +++ b/libs/bigint/run-testsuite @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash bad= diff --git a/techlibs/ice40/tests/test_bram.sh b/techlibs/ice40/tests/test_bram.sh index d4d641a9c82..5c30db64411 100644 --- a/techlibs/ice40/tests/test_bram.sh +++ b/techlibs/ice40/tests/test_bram.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex diff --git a/techlibs/ice40/tests/test_dsp_map.sh b/techlibs/ice40/tests/test_dsp_map.sh index 3f7f134e4c3..8523a4676e4 100644 --- a/techlibs/ice40/tests/test_dsp_map.sh +++ b/techlibs/ice40/tests/test_dsp_map.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex for iter in {1..100} diff --git a/techlibs/ice40/tests/test_dsp_model.sh b/techlibs/ice40/tests/test_dsp_model.sh index 1e564d1b297..c79456f7086 100644 --- a/techlibs/ice40/tests/test_dsp_model.sh +++ b/techlibs/ice40/tests/test_dsp_model.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex sed 's/SB_MAC16/SB_MAC16_UUT/; /SB_MAC16_UUT/,/endmodule/ p; d;' < ../cells_sim.v > test_dsp_model_uut.v if [ ! -f "test_dsp_model_ref.v" ]; then diff --git a/techlibs/ice40/tests/test_ffs.sh b/techlibs/ice40/tests/test_ffs.sh index ff79ec534b7..438629c3e1b 100644 --- a/techlibs/ice40/tests/test_ffs.sh +++ b/techlibs/ice40/tests/test_ffs.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex for CLKPOL in 0 1; do for ENABLE_EN in 0 1; do diff --git a/techlibs/xilinx/tests/bram1.sh b/techlibs/xilinx/tests/bram1.sh index 7451a1b3e25..7e99368048d 100644 --- a/techlibs/xilinx/tests/bram1.sh +++ b/techlibs/xilinx/tests/bram1.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e diff --git a/techlibs/xilinx/tests/bram2.sh b/techlibs/xilinx/tests/bram2.sh index 5d9c84dace1..5c18d30e246 100644 --- a/techlibs/xilinx/tests/bram2.sh +++ b/techlibs/xilinx/tests/bram2.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex unisims=/opt/Xilinx/Vivado/2014.4/data/verilog/src/unisims diff --git a/techlibs/xilinx/tests/test_dsp48_model.sh b/techlibs/xilinx/tests/test_dsp48_model.sh index 9a73f9b0c3e..bd72572d5f9 100644 --- a/techlibs/xilinx/tests/test_dsp48_model.sh +++ b/techlibs/xilinx/tests/test_dsp48_model.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex if [ -z $ISE_DIR ]; then ISE_DIR=/opt/Xilinx/ISE/14.7 diff --git a/techlibs/xilinx/tests/test_dsp48a1_model.sh b/techlibs/xilinx/tests/test_dsp48a1_model.sh index a14a78e7267..02ce2fef999 100644 --- a/techlibs/xilinx/tests/test_dsp48a1_model.sh +++ b/techlibs/xilinx/tests/test_dsp48a1_model.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex if [ -z $ISE_DIR ]; then ISE_DIR=/opt/Xilinx/ISE/14.7 diff --git a/techlibs/xilinx/tests/test_dsp_model.sh b/techlibs/xilinx/tests/test_dsp_model.sh index d005cd40c73..b2a2bb44582 100644 --- a/techlibs/xilinx/tests/test_dsp_model.sh +++ b/techlibs/xilinx/tests/test_dsp_model.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex if [ -z $VIVADO_DIR ]; then VIVADO_DIR=/opt/Xilinx/Vivado/2019.1 diff --git a/tests/aiger/run-test.sh b/tests/aiger/run-test.sh index bcf2b964a27..ca7339ff0b5 100755 --- a/tests/aiger/run-test.sh +++ b/tests/aiger/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e diff --git a/tests/arch/run-test.sh b/tests/arch/run-test.sh index 5d923db560b..a14b795090d 100755 --- a/tests/arch/run-test.sh +++ b/tests/arch/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e diff --git a/tests/asicworld/run-test.sh b/tests/asicworld/run-test.sh index c22ab69281f..5131ed64688 100755 --- a/tests/asicworld/run-test.sh +++ b/tests/asicworld/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash OPTIND=1 seed="" # default to no seed specified diff --git a/tests/blif/run-test.sh b/tests/blif/run-test.sh index 44ce7e6741f..e9698386e3a 100755 --- a/tests/blif/run-test.sh +++ b/tests/blif/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e for x in *.ys; do echo "Running $x.." diff --git a/tests/bram/run-single.sh b/tests/bram/run-single.sh index 429a79e3c6a..358423f3225 100644 --- a/tests/bram/run-single.sh +++ b/tests/bram/run-single.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e ../../yosys -qq -f verilog -p "proc; opt; memory -nomap -bram temp/brams_${2}.txt; opt -fast -full" \ -l temp/synth_${1}_${2}.log -o temp/synth_${1}_${2}.v temp/brams_${1}.v diff --git a/tests/bram/run-test.sh b/tests/bram/run-test.sh index d6ba0de4347..37fc91d0e56 100755 --- a/tests/bram/run-test.sh +++ b/tests/bram/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # run this test many times: # MAKE="make -j8" time bash -c 'for ((i=0; i<100; i++)); do echo "-- $i --"; bash run-test.sh || exit 1; done' diff --git a/tests/fmt/run-test.sh b/tests/fmt/run-test.sh index 914a7234747..6c5becd5fcf 100644 --- a/tests/fmt/run-test.sh +++ b/tests/fmt/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex diff --git a/tests/fsm/run-test.sh b/tests/fsm/run-test.sh index fbdcbf048f0..dc60c69c4ac 100755 --- a/tests/fsm/run-test.sh +++ b/tests/fsm/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # run this test many times: # time bash -c 'for ((i=0; i<100; i++)); do echo "-- $i --"; bash run-test.sh || exit 1; done' diff --git a/tests/hana/run-test.sh b/tests/hana/run-test.sh index 878c80b3f06..99be37f5efb 100755 --- a/tests/hana/run-test.sh +++ b/tests/hana/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash OPTIND=1 seed="" # default to no seed specified diff --git a/tests/liberty/run-test.sh b/tests/liberty/run-test.sh index 61f19b09bc7..19ce0667d51 100755 --- a/tests/liberty/run-test.sh +++ b/tests/liberty/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e for x in *.lib; do diff --git a/tests/lut/run-test.sh b/tests/lut/run-test.sh index f8964f146e5..a10559cac4b 100755 --- a/tests/lut/run-test.sh +++ b/tests/lut/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e for x in *.v; do echo "Running $x.." diff --git a/tests/memfile/run-test.sh b/tests/memfile/run-test.sh index e43ddd09374..db0ec54ee75 100755 --- a/tests/memfile/run-test.sh +++ b/tests/memfile/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e diff --git a/tests/memlib/run-test.sh b/tests/memlib/run-test.sh index abe88a6cbcd..5f230a03e57 100755 --- a/tests/memlib/run-test.sh +++ b/tests/memlib/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -eu OPTIND=1 diff --git a/tests/memories/run-test.sh b/tests/memories/run-test.sh index c65066a9c95..4f1da7ce7c2 100755 --- a/tests/memories/run-test.sh +++ b/tests/memories/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e diff --git a/tests/opt/run-test.sh b/tests/opt/run-test.sh index 2007cd6e4cc..74589dfeb57 100755 --- a/tests/opt/run-test.sh +++ b/tests/opt/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -eu source ../gen-tests-makefile.sh run_tests --yosys-scripts diff --git a/tests/opt_share/run-test.sh b/tests/opt_share/run-test.sh index e0008a259b2..e80cd4214d0 100755 --- a/tests/opt_share/run-test.sh +++ b/tests/opt_share/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # run this test many times: # time bash -c 'for ((i=0; i<100; i++)); do echo "-- $i --"; bash run-test.sh || exit 1; done' diff --git a/tests/proc/run-test.sh b/tests/proc/run-test.sh index 44ce7e6741f..e9698386e3a 100755 --- a/tests/proc/run-test.sh +++ b/tests/proc/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e for x in *.ys; do echo "Running $x.." diff --git a/tests/realmath/run-test.sh b/tests/realmath/run-test.sh index e1a36c694f2..833e8c3f43c 100755 --- a/tests/realmath/run-test.sh +++ b/tests/realmath/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e OPTIND=1 diff --git a/tests/rpc/run-test.sh b/tests/rpc/run-test.sh index eeb309347cf..62404375055 100755 --- a/tests/rpc/run-test.sh +++ b/tests/rpc/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e for x in *.ys; do echo "Running $x.." diff --git a/tests/select/run-test.sh b/tests/select/run-test.sh index 44ce7e6741f..e9698386e3a 100755 --- a/tests/select/run-test.sh +++ b/tests/select/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e for x in *.ys; do echo "Running $x.." diff --git a/tests/share/run-test.sh b/tests/share/run-test.sh index 1bcd8e423b8..a7b5fc4a0eb 100755 --- a/tests/share/run-test.sh +++ b/tests/share/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # run this test many times: # time bash -c 'for ((i=0; i<100; i++)); do echo "-- $i --"; bash run-test.sh || exit 1; done' diff --git a/tests/simple/run-test.sh b/tests/simple/run-test.sh index 47bcfd6da4b..b9e79f34a22 100755 --- a/tests/simple/run-test.sh +++ b/tests/simple/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash OPTIND=1 seed="" # default to no seed specified diff --git a/tests/simple_abc9/run-test.sh b/tests/simple_abc9/run-test.sh index 4a5bf01a33c..b75e54dd3fe 100755 --- a/tests/simple_abc9/run-test.sh +++ b/tests/simple_abc9/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash OPTIND=1 seed="" # default to no seed specified diff --git a/tests/smv/run-single.sh b/tests/smv/run-single.sh index a261f4ea653..a0a6eba0af2 100644 --- a/tests/smv/run-single.sh +++ b/tests/smv/run-single.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash cat > $1.tpl <&2; exit 1' ERR diff --git a/tests/various/logger_fail.sh b/tests/various/logger_fail.sh index 19b65000718..c318b648d11 100755 --- a/tests/various/logger_fail.sh +++ b/tests/various/logger_fail.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash fail() { echo "$1" >&2 diff --git a/tests/various/smtlib2_module.sh b/tests/various/smtlib2_module.sh index 491f6514877..a2206eea73d 100755 --- a/tests/various/smtlib2_module.sh +++ b/tests/various/smtlib2_module.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex ../../yosys -q -p 'read_verilog -formal smtlib2_module.v; prep; write_smt2 smtlib2_module.smt2' sed 's/; SMT-LIBv2 description generated by Yosys .*/; SMT-LIBv2 description generated by Yosys $VERSION/;s/ *$//' smtlib2_module.smt2 > smtlib2_module-filtered.smt2 diff --git a/tests/various/sv_implicit_ports.sh b/tests/various/sv_implicit_ports.sh index 9a01447f7cf..5266fffe516 100755 --- a/tests/various/sv_implicit_ports.sh +++ b/tests/various/sv_implicit_ports.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash trap 'echo "ERROR in sv_implicit_ports.sh" >&2; exit 1' ERR diff --git a/tests/various/svalways.sh b/tests/various/svalways.sh index 2cc09f801ba..7765907c708 100755 --- a/tests/various/svalways.sh +++ b/tests/various/svalways.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash trap 'echo "ERROR in svalways.sh" >&2; exit 1' ERR diff --git a/tests/verilog/dynamic_range_lhs.sh b/tests/verilog/dynamic_range_lhs.sh index 618204aed5f..77b4a291834 100755 --- a/tests/verilog/dynamic_range_lhs.sh +++ b/tests/verilog/dynamic_range_lhs.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash run() { alt=$1 diff --git a/tests/vloghtb/run-test.sh b/tests/vloghtb/run-test.sh index 3c068961991..29f7c26805d 100755 --- a/tests/vloghtb/run-test.sh +++ b/tests/vloghtb/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex diff --git a/tests/vloghtb/test_febe.sh b/tests/vloghtb/test_febe.sh index 482d44d9aa2..af0fe1b986e 100644 --- a/tests/vloghtb/test_febe.sh +++ b/tests/vloghtb/test_febe.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e source common.sh diff --git a/tests/vloghtb/test_mapopt.sh b/tests/vloghtb/test_mapopt.sh index 61528c2b4fd..c76e036a39c 100644 --- a/tests/vloghtb/test_mapopt.sh +++ b/tests/vloghtb/test_mapopt.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e source common.sh diff --git a/tests/vloghtb/test_share.sh b/tests/vloghtb/test_share.sh index 67cfe44e8de..87fac67fe39 100644 --- a/tests/vloghtb/test_share.sh +++ b/tests/vloghtb/test_share.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e source common.sh diff --git a/tests/xprop/run-test.sh b/tests/xprop/run-test.sh index db4b7ca82dd..84b7c4ac4ab 100755 --- a/tests/xprop/run-test.sh +++ b/tests/xprop/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e python3 generate.py $@ From 26644ea779e5a371b057da6270501e8649e9589f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Mon, 2 Oct 2023 11:07:02 +0200 Subject: [PATCH 02/70] equiv_simple: Drop hollow conditional All the listed flip-flop types would be known cells, so the extra part of the conditional is without effect. --- passes/equiv/equiv_simple.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/passes/equiv/equiv_simple.cc b/passes/equiv/equiv_simple.cc index 7621341a7c3..bea8876ccd1 100644 --- a/passes/equiv/equiv_simple.cc +++ b/passes/equiv/equiv_simple.cc @@ -364,7 +364,7 @@ struct EquivSimplePass : public Pass { unproven_cells_counter, GetSize(unproven_equiv_cells), log_id(module)); for (auto cell : module->cells()) { - if (!ct.cell_known(cell->type) && !cell->type.in(ID($dff), ID($_DFF_P_), ID($_DFF_N_), ID($ff), ID($_FF_))) + if (!ct.cell_known(cell->type)) continue; for (auto &conn : cell->connections()) if (yosys_celltypes.cell_output(cell->type, conn.first)) From dbf11da50a8364d1961c5b203058b518b47dff1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Mon, 2 Oct 2023 11:08:50 +0200 Subject: [PATCH 03/70] equiv_simple: Do not special-case flip-flop types in cone expansion If there's an asynchronous flip-flop type, it will be caught by not having a synchronous SAT model later on. Otherwise we can support all flip-flops. --- passes/equiv/equiv_simple.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/passes/equiv/equiv_simple.cc b/passes/equiv/equiv_simple.cc index bea8876ccd1..33b34fdbf10 100644 --- a/passes/equiv/equiv_simple.cc +++ b/passes/equiv/equiv_simple.cc @@ -60,7 +60,7 @@ struct EquivSimpleWorker for (auto &conn : cell->connections()) if (yosys_celltypes.cell_input(cell->type, conn.first)) for (auto bit : sigmap(conn.second)) { - if (cell->type.in(ID($dff), ID($_DFF_P_), ID($_DFF_N_), ID($ff), ID($_FF_))) { + if (RTLIL::builtin_ff_cell_types().count(cell->type)) { if (!conn.first.in(ID::CLK, ID::C)) next_seed.insert(bit); } else From 1f1f43edd9d7393978fb5c49de834291ace61d36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Mon, 2 Oct 2023 11:10:20 +0200 Subject: [PATCH 04/70] equiv_simple: Fix seed handling in non-short mode --- passes/equiv/equiv_simple.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/passes/equiv/equiv_simple.cc b/passes/equiv/equiv_simple.cc index 33b34fdbf10..2c9d829140b 100644 --- a/passes/equiv/equiv_simple.cc +++ b/passes/equiv/equiv_simple.cc @@ -133,11 +133,9 @@ struct EquivSimpleWorker for (auto bit_a : seed_a) find_input_cone(next_seed_a, full_cells_cone_a, full_bits_cone_a, no_stop_cells, no_stop_bits, nullptr, bit_a); - next_seed_a.clear(); for (auto bit_b : seed_b) find_input_cone(next_seed_b, full_cells_cone_b, full_bits_cone_b, no_stop_cells, no_stop_bits, nullptr, bit_b); - next_seed_b.clear(); pool short_cells_cone_a, short_cells_cone_b; pool short_bits_cone_a, short_bits_cone_b; @@ -145,10 +143,12 @@ struct EquivSimpleWorker if (short_cones) { + next_seed_a.clear(); for (auto bit_a : seed_a) find_input_cone(next_seed_a, short_cells_cone_a, short_bits_cone_a, full_cells_cone_b, full_bits_cone_b, &input_bits, bit_a); next_seed_a.swap(seed_a); + next_seed_b.clear(); for (auto bit_b : seed_b) find_input_cone(next_seed_b, short_cells_cone_b, short_bits_cone_b, full_cells_cone_a, full_bits_cone_a, &input_bits, bit_b); next_seed_b.swap(seed_b); From f19c6b44153850f62d385e28f811a10489487f2a Mon Sep 17 00:00:00 2001 From: Pepijn de Vos Date: Sun, 3 Dec 2023 10:17:28 +0100 Subject: [PATCH 05/70] Enable bram for Gowin --- techlibs/gowin/synth_gowin.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/techlibs/gowin/synth_gowin.cc b/techlibs/gowin/synth_gowin.cc index 302ba76e5af..85022c1cfa2 100644 --- a/techlibs/gowin/synth_gowin.cc +++ b/techlibs/gowin/synth_gowin.cc @@ -130,7 +130,6 @@ struct SynthGowinPass : public ScriptPass } if (args[argidx] == "-json" && argidx+1 < args.size()) { json_file = args[++argidx]; - nobram = true; continue; } if (args[argidx] == "-run" && argidx+1 < args.size()) { From 449e3dbbd361b13e56f19b5ee8250c5bb7a2e283 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Wed, 13 Dec 2023 18:21:37 +0100 Subject: [PATCH 06/70] cxxrtl: Mask `bmux` result appropriately --- backends/cxxrtl/runtime/cxxrtl/cxxrtl.h | 1 + tests/cxxrtl/test_value.cc | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index d861b7e072a..78dbf37072d 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -475,6 +475,7 @@ struct value : public expr_base> { carry = (shift_bits == 0) ? 0 : data[result.chunks + shift_chunks - 1 - n] << (chunk::bits - shift_bits); } + result.data[result.chunks - 1] &= result.msb_mask; return result; } diff --git a/tests/cxxrtl/test_value.cc b/tests/cxxrtl/test_value.cc index 4d68372bbd1..0750d412e4e 100644 --- a/tests/cxxrtl/test_value.cc +++ b/tests/cxxrtl/test_value.cc @@ -42,4 +42,11 @@ int main() cxxrtl::value<32> a(0x00040000u); assert(a.ctlz() == 13); } + + { + // bmux clears top bits of result + cxxrtl::value<8> val(0x1fu); + cxxrtl::value<1> sel(0u); + assert(val.template bmux<4>(sel).get() == 0xfu); + } } From 111085669ba3f5cdd3bf22db8774378239c49094 Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Thu, 14 Dec 2023 16:42:48 +0100 Subject: [PATCH 07/70] smtbmc: Use fewer smt commands while writing .yw traces Depending on the used solver and design this can be a signficant performance improvement. --- backends/smt2/smtbmc.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/backends/smt2/smtbmc.py b/backends/smt2/smtbmc.py index dd3f9ac48dc..34657be264a 100644 --- a/backends/smt2/smtbmc.py +++ b/backends/smt2/smtbmc.py @@ -1327,6 +1327,9 @@ def write_yw_trace(steps, index, allregs=False, filename=None): sig = yw.add_sig(word_path, overlap_start, overlap_end - overlap_start, True) mem_init_values.append((sig, overlap_bits.replace("x", "?"))) + exprs = [] + all_sigs = [] + for i, k in enumerate(steps): step_values = WitnessValues() @@ -1337,8 +1340,15 @@ def write_yw_trace(steps, index, allregs=False, filename=None): else: sigs = seqs + exprs.extend(smt.witness_net_expr(topmod, f"s{k}", sig) for sig in sigs) + + all_sigs.append(sigs) + + bvs = iter(smt.get_list(exprs)) + + for sigs in all_sigs: for sig in sigs: - value = smt.bv2bin(smt.get(smt.witness_net_expr(topmod, f"s{k}", sig))) + value = smt.bv2bin(next(bvs)) step_values[sig["sig"]] = value yw.step(step_values) From 3fab4d42ec72de70dab01ee99fd7891b74ac6d69 Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Thu, 14 Dec 2023 16:44:21 +0100 Subject: [PATCH 08/70] smtbmc: Allow raw SMT-LIBv2 comamnds and expressions for --incremental --- backends/smt2/smtbmc_incremental.py | 43 +++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/backends/smt2/smtbmc_incremental.py b/backends/smt2/smtbmc_incremental.py index 2be4fb6799f..1a2a4570312 100644 --- a/backends/smt2/smtbmc_incremental.py +++ b/backends/smt2/smtbmc_incremental.py @@ -194,9 +194,31 @@ def expr_yw(self, expr, smt_out): return "Bool" + def expr_smtlib(self, expr, smt_out): + self.expr_arg_len(expr, 2) + + smtlib_expr = expr[1] + sort = expr[2] + + if not isinstance(smtlib_expr, str): + raise InteractiveError( + "raw SMT-LIB expression has to be a string, " + f"got {json.dumps(smtlib_expr)}" + ) + + if not isinstance(sort, str): + raise InteractiveError( + f"raw SMT-LIB sort has to be a string, got {json.dumps(sort)}" + ) + + smt_out.append(smtlib_expr) + return sort + def expr_label(self, expr, smt_out): if len(expr) != 3: - raise InteractiveError(f'expected ["!", label, sub_expr], got {expr!r}') + raise InteractiveError( + f'expected ["!", label, sub_expr], got {json.dumps(expr)}' + ) label = expr[1] subexpr = expr[2] @@ -226,6 +248,7 @@ def expr_label(self, expr, smt_out): "or": expr_andor, "=": expr_eq, "yw": expr_yw, + "smtlib": expr_smtlib, "!": expr_label, } @@ -251,7 +274,8 @@ def expr(self, expr, smt_out, required_sort=None): ) ): raise InteractiveError( - f"required sort {json.dumps(required_sort)} found sort {json.dumps(sort)}" + f"required sort {json.dumps(required_sort)} " + f"found sort {json.dumps(sort)}" ) return sort raise InteractiveError(f"unknown expression {json.dumps(expr[0])}") @@ -287,6 +311,14 @@ def cmd_pop(self, cmd): def cmd_check(self, cmd): return smtbmc.smt_check_sat() + def cmd_smtlib(self, cmd): + command = cmd.get("command") + if not isinstance(command, str): + raise InteractiveError( + f"raw SMT-LIB command must be a string, found {json.dumps(command)}" + ) + smtbmc.smt.write(command) + def cmd_design_hierwitness(self, cmd=None): allregs = (cmd is None) or bool(cmd.get("allreges", False)) if self._cached_hierwitness[allregs] is not None: @@ -326,13 +358,17 @@ def cmd_read_yw_trace(self, cmd): map_steps = {i: int(j) for i, j in enumerate(steps)} - smtbmc.ywfile_constraints(path, constraints, map_steps=map_steps, skip_x=skip_x) + last_step = smtbmc.ywfile_constraints( + path, constraints, map_steps=map_steps, skip_x=skip_x + ) self._yw_constraints[name] = { map_steps.get(i, i): [smtexpr for cexfile, smtexpr in constraint_list] for i, constraint_list in constraints.items() } + return dict(last_step=last_step) + def cmd_ping(self, cmd): return cmd @@ -344,6 +380,7 @@ def cmd_ping(self, cmd): "push": cmd_push, "pop": cmd_pop, "check": cmd_check, + "smtlib": cmd_smtlib, "design_hierwitness": cmd_design_hierwitness, "write_yw_trace": cmd_write_yw_trace, "read_yw_trace": cmd_read_yw_trace, From 94d7c22714dad09b24321835c36ec3100f73f9d7 Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Thu, 14 Dec 2023 16:45:19 +0100 Subject: [PATCH 09/70] yosys-witness: Add aiw2yw --present-only to omit unused signals --- backends/smt2/witness.py | 53 +++++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/backends/smt2/witness.py b/backends/smt2/witness.py index a39500c2dc1..b7e25851cbb 100644 --- a/backends/smt2/witness.py +++ b/backends/smt2/witness.py @@ -183,7 +183,8 @@ def __init__(self, mapfile): @click.argument("mapfile", type=click.File("r")) @click.argument("output", type=click.File("w")) @click.option("--skip-x", help="Leave input x bits unassigned.", is_flag=True) -def aiw2yw(input, mapfile, output, skip_x): +@click.option("--present-only", help="Only include bits present in at least one time step.", is_flag=True) +def aiw2yw(input, mapfile, output, skip_x, present_only): input_name = input.name click.echo(f"Converting AIGER witness trace {input_name!r} to Yosys witness trace {output.name!r}...") click.echo(f"Using Yosys witness AIGER map file {mapfile.name!r}") @@ -211,16 +212,23 @@ def aiw2yw(input, mapfile, output, skip_x): if not re.match(r'[0]*$', ffline): raise click.ClickException(f"{input_name}: non-default initial state not supported") - outyw = WriteWitness(output, 'yosys-witness aiw2yw') + if not present_only: + outyw = WriteWitness(output, "yosys-witness aiw2yw") - for clock in aiger_map.clocks: - outyw.add_clock(clock["path"], clock["offset"], clock["edge"]) + for clock in aiger_map.clocks: + outyw.add_clock(clock["path"], clock["offset"], clock["edge"]) - for (path, offset), id in aiger_map.sigmap.bit_to_id.items(): - outyw.add_sig(path, offset, init_only=id in aiger_map.init_inputs) + for (path, offset), id in aiger_map.sigmap.bit_to_id.items(): + outyw.add_sig(path, offset, init_only=id in aiger_map.init_inputs) missing = set() + seen = set() + + buffered_steps = [] + + skip = "x?" if skip_x else "?" + t = -1 while True: inline = next(input, None) if inline is None: @@ -232,17 +240,20 @@ def aiw2yw(input, mapfile, output, skip_x): if inline.startswith("#"): continue - if not re.match(r'[01x]*$', ffline): + t += 1 + + if not re.match(r"[01x]*$", inline): raise click.ClickException(f"{input_name}: unexpected data in AIGER witness file") if len(inline) != aiger_map.input_count: raise click.ClickException( - f"{input_name}: {mapfile.name}: number of inputs does not match, " - f"{len(inline)} in witness, {aiger_map.input_count} in map file") + f"{input_name}: {mapfile.name}: number of inputs does not match, " + f"{len(inline)} in witness, {aiger_map.input_count} in map file" + ) values = WitnessValues() for i, v in enumerate(inline): - if outyw.t > 0 and i in aiger_map.init_inputs: + if v in skip or (t > 0 and i in aiger_map.init_inputs): continue try: @@ -250,11 +261,29 @@ def aiw2yw(input, mapfile, output, skip_x): except IndexError: bit = None if bit is None: - missing.insert(i) + missing.add(i) + elif present_only: + seen.add(i) values[bit] = v - outyw.step(values, skip_x=skip_x) + if present_only: + buffered_steps.append(values) + else: + outyw.step(values) + + if present_only: + outyw = WriteWitness(output, "yosys-witness aiw2yw") + + for clock in aiger_map.clocks: + outyw.add_clock(clock["path"], clock["offset"], clock["edge"]) + + for (path, offset), id in aiger_map.sigmap.bit_to_id.items(): + if id in seen: + outyw.add_sig(path, offset, init_only=id in aiger_map.init_inputs) + + for values in buffered_steps: + outyw.step(values) outyw.end_trace() From 70d35314dbd7521870047ed607897f22dc48cbc3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 00:16:38 +0000 Subject: [PATCH 10/70] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 446682e86e9..e367846e2f7 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LDLIBS += -lrt endif endif -YOSYS_VER := 0.36+40 +YOSYS_VER := 0.36+42 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo From fc5c5172f81189b67a93f85a189bf77f04fda971 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Wed, 20 Dec 2023 23:23:02 +0100 Subject: [PATCH 11/70] lattice: Fix mapping onto DP8KC for data width 1 or 2 --- techlibs/lattice/brams_map_8kc.v | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/techlibs/lattice/brams_map_8kc.v b/techlibs/lattice/brams_map_8kc.v index 6783e5b2902..b57250fc7a6 100644 --- a/techlibs/lattice/brams_map_8kc.v +++ b/techlibs/lattice/brams_map_8kc.v @@ -38,8 +38,20 @@ endfunction wire [8:0] DOA; wire [8:0] DOB; -wire [8:0] DIA = PORT_A_WR_DATA; -wire [8:0] DIB = PORT_B_WR_DATA; +wire [8:0] DIA; +wire [8:0] DIB; + +case(PORT_A_WIDTH) + 1: assign DIA = {7'bx, PORT_A_WR_DATA[0], 1'bx}; + 2: assign DIA = {3'bx, PORT_A_WR_DATA[1], 2'bx, PORT_A_WR_DATA[0], 2'bx}; + default: assign DIA = PORT_A_WR_DATA; +endcase + +case(PORT_B_WIDTH) + 1: assign DIB = {7'bx, PORT_B_WR_DATA[0], 1'bx}; + 2: assign DIB = {3'bx, PORT_B_WR_DATA[1], 2'bx, PORT_B_WR_DATA[0], 2'bx}; + default: assign DIB = PORT_B_WR_DATA; +endcase assign PORT_A_RD_DATA = DOA; assign PORT_B_RD_DATA = DOB; From c028f251585b1e42a7c493afb4b428408daa47c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Thu, 21 Dec 2023 10:22:52 +0100 Subject: [PATCH 12/70] lattice: Disable broken port configuration in bram inference --- techlibs/lattice/brams_8kc.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/techlibs/lattice/brams_8kc.txt b/techlibs/lattice/brams_8kc.txt index 3afbeda07e6..f254c46aa82 100644 --- a/techlibs/lattice/brams_8kc.txt +++ b/techlibs/lattice/brams_8kc.txt @@ -32,6 +32,10 @@ ram block $__PDPW8KC_ { cost 64; init no_undef; port sr "R" { + # width 2 cannot be supported because of quirks + # of the primitive, and memlib requires us to + # remove width 1 as well + width 4 9 18; clock posedge; clken; option "RESETMODE" "SYNC" { From ea7818d31bb2533d4ecceb2ed1bcf4a22b850453 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 22 Dec 2023 00:15:54 +0000 Subject: [PATCH 13/70] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e367846e2f7..00569d95c92 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LDLIBS += -lrt endif endif -YOSYS_VER := 0.36+42 +YOSYS_VER := 0.36+58 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo From fb72dc1a4085547bce2a4d1b9a69b99a9f1ab784 Mon Sep 17 00:00:00 2001 From: Claire Xenia Wolf Date: Fri, 29 Dec 2023 19:20:44 +0100 Subject: [PATCH 14/70] Add constexpr hashlib default constructors Signed-off-by: Claire Xenia Wolf --- kernel/hashlib.h | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/kernel/hashlib.h b/kernel/hashlib.h index 47aba71a834..25aa94b8002 100644 --- a/kernel/hashlib.h +++ b/kernel/hashlib.h @@ -420,7 +420,7 @@ class dict operator const_iterator() const { return const_iterator(ptr, index); } }; - dict() + constexpr dict() { } @@ -855,7 +855,7 @@ class pool operator const_iterator() const { return const_iterator(ptr, index); } }; - pool() + constexpr pool() { } @@ -1062,6 +1062,10 @@ class idict const K *operator->() const { return &container[index]; } }; + constexpr idict() + { + } + int operator()(const K &key) { int hash = database.do_hash(key); @@ -1132,6 +1136,10 @@ class mfp public: typedef typename idict::const_iterator const_iterator; + constexpr mfp() + { + } + int operator()(const K &key) const { int i = database(key); From df65634e07d283202bebfae2e2110724a4d8003f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 30 Dec 2023 00:15:15 +0000 Subject: [PATCH 15/70] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 00569d95c92..60d67d05a3d 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LDLIBS += -lrt endif endif -YOSYS_VER := 0.36+58 +YOSYS_VER := 0.36+61 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo From 627fbc3477a4f11dd9824b4385ec6b776824ae5d Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Tue, 2 Jan 2024 11:26:48 +0100 Subject: [PATCH 16/70] Fix Windows build by forcing initialization order, fixes #4068 --- techlibs/quicklogic/ql_bram_merge.cc | 6 +-- techlibs/quicklogic/ql_dsp_io_regs.cc | 9 ++-- techlibs/quicklogic/ql_dsp_simd.cc | 70 +++++++++++++-------------- 3 files changed, 43 insertions(+), 42 deletions(-) diff --git a/techlibs/quicklogic/ql_bram_merge.cc b/techlibs/quicklogic/ql_bram_merge.cc index 1098bc8f696..bed1ba572df 100644 --- a/techlibs/quicklogic/ql_bram_merge.cc +++ b/techlibs/quicklogic/ql_bram_merge.cc @@ -31,9 +31,6 @@ PRIVATE_NAMESPACE_BEGIN struct QlBramMergeWorker { - const RTLIL::IdString split_cell_type = ID($__QLF_TDP36K); - const RTLIL::IdString merged_cell_type = ID($__QLF_TDP36K_MERGED); - // can be used to record parameter values that have to match on both sides typedef dict MergeableGroupKeyType; @@ -42,6 +39,8 @@ struct QlBramMergeWorker { QlBramMergeWorker(RTLIL::Module* module) : module(module) { + const RTLIL::IdString split_cell_type = ID($__QLF_TDP36K); + for (RTLIL::Cell* cell : module->selected_cells()) { if(cell->type != split_cell_type) continue; @@ -125,6 +124,7 @@ struct QlBramMergeWorker { void merge_brams(RTLIL::Cell* bram1, RTLIL::Cell* bram2) { + const RTLIL::IdString merged_cell_type = ID($__QLF_TDP36K_MERGED); // Create the new cell RTLIL::Cell* merged = module->addCell(NEW_ID, merged_cell_type); diff --git a/techlibs/quicklogic/ql_dsp_io_regs.cc b/techlibs/quicklogic/ql_dsp_io_regs.cc index 523c86e7341..ecf163dbf6b 100644 --- a/techlibs/quicklogic/ql_dsp_io_regs.cc +++ b/techlibs/quicklogic/ql_dsp_io_regs.cc @@ -30,10 +30,6 @@ PRIVATE_NAMESPACE_BEGIN // ============================================================================ struct QlDspIORegs : public Pass { - const std::vector ports2del_mult = {ID(load_acc), ID(subtract), ID(acc_fir), ID(dly_b), - ID(saturate_enable), ID(shift_right), ID(round)}; - const std::vector ports2del_mult_acc = {ID(acc_fir), ID(dly_b)}; - SigMap sigmap; // .......................................... @@ -67,6 +63,11 @@ struct QlDspIORegs : public Pass { void ql_dsp_io_regs_pass(RTLIL::Module *module) { + static const std::vector ports2del_mult = {ID(load_acc), ID(subtract), ID(acc_fir), ID(dly_b), + ID(saturate_enable), ID(shift_right), ID(round)}; + static const std::vector ports2del_mult_acc = {ID(acc_fir), ID(dly_b)}; + + sigmap.set(module); for (auto cell : module->cells()) { diff --git a/techlibs/quicklogic/ql_dsp_simd.cc b/techlibs/quicklogic/ql_dsp_simd.cc index 153f3995f64..9df979c3362 100644 --- a/techlibs/quicklogic/ql_dsp_simd.cc +++ b/techlibs/quicklogic/ql_dsp_simd.cc @@ -60,43 +60,11 @@ struct QlDspSimdPass : public Pass { // .......................................... - // DSP control and config ports to consider and how to map them to ports - // of the target DSP cell - const std::vector> m_DspCfgPorts = { - std::make_pair(ID(clock_i), ID(clk)), - std::make_pair(ID(reset_i), ID(reset)), - std::make_pair(ID(feedback_i), ID(feedback)), - std::make_pair(ID(load_acc_i), ID(load_acc)), - std::make_pair(ID(unsigned_a_i), ID(unsigned_a)), - std::make_pair(ID(unsigned_b_i), ID(unsigned_b)), - std::make_pair(ID(subtract_i), ID(subtract)), - std::make_pair(ID(output_select_i), ID(output_select)), - std::make_pair(ID(saturate_enable_i), ID(saturate_enable)), - std::make_pair(ID(shift_right_i), ID(shift_right)), - std::make_pair(ID(round_i), ID(round)), - std::make_pair(ID(register_inputs_i), ID(register_inputs)) - }; - const int m_ModeBitsSize = 80; - // DSP data ports and how to map them to ports of the target DSP cell - const std::vector> m_DspDataPorts = { - std::make_pair(ID(a_i), ID(a)), - std::make_pair(ID(b_i), ID(b)), - std::make_pair(ID(acc_fir_i), ID(acc_fir)), - std::make_pair(ID(z_o), ID(z)), - std::make_pair(ID(dly_b_o), ID(dly_b)) - }; - // DSP parameters const std::vector m_DspParams = {"COEFF_3", "COEFF_2", "COEFF_1", "COEFF_0"}; - // Source DSP cell type (SISD) - const IdString m_SisdDspType = ID(dsp_t1_10x9x32); - - // Target DSP cell types for the SIMD mode - const IdString m_SimdDspType = ID(QL_DSP2); - /// Temporary SigBit to SigBit helper map. SigMap sigmap; @@ -106,6 +74,38 @@ struct QlDspSimdPass : public Pass { { log_header(a_Design, "Executing QL_DSP_SIMD pass.\n"); + // DSP control and config ports to consider and how to map them to ports + // of the target DSP cell + static const std::vector> m_DspCfgPorts = { + std::make_pair(ID(clock_i), ID(clk)), + std::make_pair(ID(reset_i), ID(reset)), + std::make_pair(ID(feedback_i), ID(feedback)), + std::make_pair(ID(load_acc_i), ID(load_acc)), + std::make_pair(ID(unsigned_a_i), ID(unsigned_a)), + std::make_pair(ID(unsigned_b_i), ID(unsigned_b)), + std::make_pair(ID(subtract_i), ID(subtract)), + std::make_pair(ID(output_select_i), ID(output_select)), + std::make_pair(ID(saturate_enable_i), ID(saturate_enable)), + std::make_pair(ID(shift_right_i), ID(shift_right)), + std::make_pair(ID(round_i), ID(round)), + std::make_pair(ID(register_inputs_i), ID(register_inputs)) + }; + + // DSP data ports and how to map them to ports of the target DSP cell + static const std::vector> m_DspDataPorts = { + std::make_pair(ID(a_i), ID(a)), + std::make_pair(ID(b_i), ID(b)), + std::make_pair(ID(acc_fir_i), ID(acc_fir)), + std::make_pair(ID(z_o), ID(z)), + std::make_pair(ID(dly_b_o), ID(dly_b)) + }; + + // Source DSP cell type (SISD) + static const IdString m_SisdDspType = ID(dsp_t1_10x9x32); + + // Target DSP cell types for the SIMD mode + static const IdString m_SimdDspType = ID(QL_DSP2); + // Parse args extra_args(a_Args, 1, a_Design); @@ -126,7 +126,7 @@ struct QlDspSimdPass : public Pass { continue; // Add to a group - const auto key = getDspConfig(cell); + const auto key = getDspConfig(cell, m_DspCfgPorts); groups[key].push_back(cell); } @@ -255,11 +255,11 @@ struct QlDspSimdPass : public Pass { } /// Given a DSP cell populates and returns a DspConfig struct for it. - DspConfig getDspConfig(RTLIL::Cell *a_Cell) + DspConfig getDspConfig(RTLIL::Cell *a_Cell, const std::vector> &dspCfgPorts) { DspConfig config; - for (const auto &it : m_DspCfgPorts) { + for (const auto &it : dspCfgPorts) { auto port = it.first; // Port unconnected From 1bbea13f80fa7e5d337dcf3affd28d1ab8664cae Mon Sep 17 00:00:00 2001 From: Dag Lem Date: Thu, 4 Jan 2024 17:22:07 +0100 Subject: [PATCH 17/70] Correct hierarchical path names for structs and unions --- frontends/ast/simplify.cc | 36 ++++++++++++++++++--------------- tests/svtypes/typedef_scopes.sv | 6 +++--- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc index 2a500b56bd3..dfa1ed6af03 100644 --- a/frontends/ast/simplify.cc +++ b/frontends/ast/simplify.cc @@ -226,17 +226,6 @@ void AstNode::annotateTypedEnums(AstNode *template_node) } } -static bool name_has_dot(const std::string &name, std::string &struct_name) -{ - // check if plausible struct member name \sss.mmm - std::string::size_type pos; - if (name.substr(0, 1) == "\\" && (pos = name.find('.', 0)) != std::string::npos) { - struct_name = name.substr(0, pos); - return true; - } - return false; -} - static AstNode *make_range(int left, int right, bool is_signed = false) { // generate a pre-validated range node for a fixed signal range. @@ -2185,11 +2174,24 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin if (type == AST_IDENTIFIER && !basic_prep) { // check if a plausible struct member sss.mmmm - std::string sname; - if (name_has_dot(str, sname)) { - if (current_scope.count(str) > 0) { - auto item_node = current_scope[str]; - if (item_node->type == AST_STRUCT_ITEM || item_node->type == AST_STRUCT || item_node->type == AST_UNION) { + if (!str.empty() && str[0] == '\\' && current_scope.count(str)) { + auto item_node = current_scope[str]; + if (item_node->type == AST_STRUCT_ITEM || item_node->type == AST_STRUCT || item_node->type == AST_UNION) { + // Traverse any hierarchical path until the full name for the referenced struct/union is found. + std::string sname; + bool found_sname = false; + for (std::string::size_type pos = 0; (pos = str.find('.', pos)) != std::string::npos; pos++) { + sname = str.substr(0, pos); + if (current_scope.count(sname)) { + auto stype = current_scope[sname]->type; + if (stype == AST_WIRE || stype == AST_PARAMETER || stype == AST_LOCALPARAM) { + found_sname = true; + break; + } + } + } + + if (found_sname) { // structure member, rewrite this node to reference the packed struct wire auto range = make_struct_member_range(this, item_node); newNode = new AstNode(AST_IDENTIFIER, range); @@ -4681,6 +4683,8 @@ void AstNode::expand_genblock(const std::string &prefix) switch (child->type) { case AST_WIRE: case AST_MEMORY: + case AST_STRUCT: + case AST_UNION: case AST_PARAMETER: case AST_LOCALPARAM: case AST_FUNCTION: diff --git a/tests/svtypes/typedef_scopes.sv b/tests/svtypes/typedef_scopes.sv index 5ac9a4664c4..cd7b7953e03 100644 --- a/tests/svtypes/typedef_scopes.sv +++ b/tests/svtypes/typedef_scopes.sv @@ -45,12 +45,12 @@ module top; localparam W = 10; typedef T U; typedef logic [W-1:0] V; - struct packed { + typedef struct packed { logic [W-1:0] x; // width 10 U y; // width 5 V z; // width 10 - } shadow; - // This currently only works as long as long as shadow is not typedef'ed + } shadow_t; + shadow_t shadow; always @(*) assert($bits(shadow.x) == 10); always @(*) assert($bits(shadow.y) == 5); always @(*) assert($bits(shadow.z) == 10); From a94fafa8fe70650a1e8ded35e352e9fb48b27639 Mon Sep 17 00:00:00 2001 From: Catherine Date: Fri, 8 Dec 2023 18:21:19 +0000 Subject: [PATCH 18/70] cxxrtl: add a representation of simulation timestamps. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While the VCD format separates the timescale and the timestep (likely to allow representing the timestep with a small integer type), time in CXXRTL is represented using a uniform 96-bit number, which allows for a ±100 year range at femtosecond resolution. The implementation uses `value<96>`, which provides fast arithmetic and comparison operations, as well as conversion to/from a more common representation of integer seconds plus femtoseconds. --- backends/cxxrtl/runtime/cxxrtl/cxxrtl_time.h | 229 +++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 backends/cxxrtl/runtime/cxxrtl/cxxrtl_time.h diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl_time.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl_time.h new file mode 100644 index 00000000000..51f59321e2d --- /dev/null +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl_time.h @@ -0,0 +1,229 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2023 Catherine + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef CXXRTL_TIME_H +#define CXXRTL_TIME_H + +#include +#include + +#include + +namespace cxxrtl { + +// A timestamp or a difference in time, stored as a 96-bit number of femtoseconds (10e-15 s). The dynamic range and +// resolution of this format can represent any VCD timestamp within 136 years, without the need for a timescale. +class time { +public: + static constexpr size_t bits = 96; // 3 chunks + +private: + static constexpr value resolution = value { + chunk_t(1000000000000000ull & 0xffffffffull), chunk_t(1000000000000000ull >> 32), 0u + }; + static constexpr size_t resolution_digits = 15; + + // Signed number of femtoseconds from the beginning of time. + value raw; + +public: + constexpr time() {} + + explicit constexpr time(const value &raw) : raw(raw) {} + explicit operator const value &() const { return raw; } + + static constexpr time maximum() { + return time(value { 0xffffffffu, 0xffffffffu, 0x7fffffffu }); + } + + time(int32_t secs, int64_t femtos) { + value<32> secs_val; + secs_val.set((uint32_t)secs); + value<64> femtos_val; + femtos_val.set((uint64_t)femtos); + raw = secs_val.sext().mul(resolution).add(femtos_val.sext()); + } + + bool is_zero() const { + return raw.is_zero(); + } + + // Extracts the sign of the value. + bool is_negative() const { + return raw.is_neg(); + } + + // Extracts the absolute number of whole seconds. Negative if the value is negative. + int32_t secs() const { + return raw.sdivmod(resolution).first.trunc<32>().get(); + } + + // Extracts the absolute number of femtoseconds in the fractional second. Negative if the value is negative. + int64_t femtos() const { + return raw.sdivmod(resolution).second.trunc<64>().get(); + } + + bool operator==(const time &other) const { + return raw == other.raw; + } + + bool operator!=(const time &other) const { + return raw != other.raw; + } + + bool operator>(const time &other) const { + return other.raw.scmp(raw); + } + + bool operator>=(const time &other) const { + return !raw.scmp(other.raw); + } + + bool operator<(const time &other) const { + return raw.scmp(other.raw); + } + + bool operator<=(const time &other) const { + return !other.raw.scmp(raw); + } + + time operator+(const time &other) const { + return time(raw.add(other.raw)); + } + + time &operator+=(const time &other) { + *this = *this + other; + return *this; + } + + time operator-() const { + return time(raw.neg()); + } + + time operator-(const time &other) const { + return *this + (-other); + } + + time &operator-=(const time &other) { + *this = *this - other; + return *this; + } + + operator std::string() const { + char buf[38]; // len(f"-{2**64}.{10**15-1}") + 1 == 38 + int32_t secs = this->secs(); + int64_t femtos = this->femtos(); + snprintf(buf, sizeof(buf), "%s%" PRIi32 ".%015" PRIi64, + is_negative() ? "-" : "", secs >= 0 ? secs : -secs, femtos >= 0 ? femtos : -femtos); + return buf; + } + +#if __cplusplus >= 201603L + [[nodiscard("ignoring parse errors")]] +#endif + bool parse(const std::string &str) { + enum { + parse_sign_opt, + parse_integral, + parse_fractional, + } state = parse_sign_opt; + bool negative = false; + int32_t integral = 0; + int64_t fractional = 0; + size_t frac_digits = 0; + for (auto chr : str) { + switch (state) { + case parse_sign_opt: + state = parse_integral; + if (chr == '+' || chr == '-') { + negative = (chr == '-'); + break; + } + /* fallthrough */ + case parse_integral: + if (chr >= '0' && chr <= '9') { + integral *= 10; + integral += chr - '0'; + } else if (chr == '.') { + state = parse_fractional; + } else { + return false; + } + break; + case parse_fractional: + if (chr >= '0' && chr <= '9' && frac_digits < resolution_digits) { + fractional *= 10; + fractional += chr - '0'; + frac_digits++; + } else { + return false; + } + break; + } + } + if (frac_digits == 0) + return false; + while (frac_digits++ < resolution_digits) + fractional *= 10; + *this = negative ? -time { integral, fractional} : time { integral, fractional }; + return true; + } +}; + +// Out-of-line definition required until C++17. +constexpr value time::resolution; + +std::ostream &operator<<(std::ostream &os, const time &val) { + os << (std::string)val; + return os; +} + +// These literals are (confusingly) compatible with the ones from `std::chrono`: the `std::chrono` literals do not +// have an underscore (e.g. 1ms) and the `cxxrtl::time` literals do (e.g. 1_ms). This syntactic difference is +// a requirement of the C++ standard. Despite being compatible the literals should not be mixed in the same namespace. +namespace time_literals { + +time operator""_s(unsigned long long seconds) { + return time { (int32_t)seconds, 0 }; +} + +time operator""_ms(unsigned long long milliseconds) { + return time { 0, (int64_t)milliseconds * 1000000000000 }; +} + +time operator""_us(unsigned long long microseconds) { + return time { 0, (int64_t)microseconds * 1000000000 }; +} + +time operator""_ns(unsigned long long nanoseconds) { + return time { 0, (int64_t)nanoseconds * 1000000 }; +} + +time operator""_ps(unsigned long long picoseconds) { + return time { 0, (int64_t)picoseconds * 1000 }; +} + +time operator""_fs(unsigned long long femtoseconds) { + return time { 0, (int64_t)femtoseconds }; +} + +}; + +}; + +#endif From 3e358d9bfa9289fefbc68e83ab40c80f4b123641 Mon Sep 17 00:00:00 2001 From: Catherine Date: Wed, 25 Oct 2023 10:51:39 +0000 Subject: [PATCH 19/70] cxxrtl: add a way to observe state changes during the commit step. The commit observer is a structure containing a callback that is invoked whenever the `commit()` method changes a wire or a memory. This allows code external to the compiled netlist to react to changes in the design state in a very efficient way. One example of how this feature can be used is an efficient implementation of record/replay. Note that the VCD writer does not benefit from this feature because it must be able to react to changes in any debug items and not just those that contain design state. --- backends/cxxrtl/cxxrtl_backend.cc | 42 ++++++++++++++++--------- backends/cxxrtl/runtime/cxxrtl/cxxrtl.h | 40 +++++++++++++++++++++-- 2 files changed, 65 insertions(+), 17 deletions(-) diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index 1ab865a279c..2c35a1943e2 100644 --- a/backends/cxxrtl/cxxrtl_backend.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -2115,19 +2115,19 @@ struct CxxrtlWorker { if (wire_type.type == WireType::MEMBER && edge_wires[wire]) f << indent << "prev_" << mangle(wire) << " = " << mangle(wire) << ";\n"; if (wire_type.is_buffered()) - f << indent << "if (" << mangle(wire) << ".commit()) changed = true;\n"; + f << indent << "if (" << mangle(wire) << ".commit(observer)) changed = true;\n"; } if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) { for (auto &mem : mod_memories[module]) { if (!writable_memories.count({module, mem.memid})) continue; - f << indent << "if (" << mangle(&mem) << ".commit()) changed = true;\n"; + f << indent << "if (" << mangle(&mem) << ".commit(observer)) changed = true;\n"; } for (auto cell : module->cells()) { if (is_internal_cell(cell->type)) continue; const char *access = is_cxxrtl_blackbox_cell(cell) ? "->" : "."; - f << indent << "if (" << mangle(cell) << access << "commit()) changed = true;\n"; + f << indent << "if (" << mangle(cell) << access << "commit(observer)) changed = true;\n"; } } f << indent << "return changed;\n"; @@ -2146,7 +2146,7 @@ struct CxxrtlWorker { if (!metadata_item.first.isPublic()) continue; if (metadata_item.second.size() > 64 && (metadata_item.second.flags & RTLIL::CONST_FLAG_STRING) == 0) { - f << indent << "/* attribute " << metadata_item.first.str().substr(1) << " is over 64 bits wide */"; + f << indent << "/* attribute " << metadata_item.first.str().substr(1) << " is over 64 bits wide */\n"; continue; } f << indent << "{ " << escape_cxx_string(metadata_item.first.str().substr(1)) << ", "; @@ -2371,16 +2371,22 @@ struct CxxrtlWorker { dump_eval_method(module); f << indent << "}\n"; f << "\n"; - f << indent << "bool commit() override {\n"; + f << indent << "template\n"; + f << indent << "bool commit(ObserverT &observer) {\n"; dump_commit_method(module); f << indent << "}\n"; f << "\n"; + f << indent << "bool commit() override {\n"; + f << indent << indent << "null_observer observer;\n"; + f << indent << indent << "return commit<>(observer);\n"; + f << indent << "}\n"; if (debug_info) { + f << "\n"; f << indent << "void debug_info(debug_items &items, std::string path = \"\") override {\n"; dump_debug_info_method(module); f << indent << "}\n"; - f << "\n"; } + f << "\n"; f << indent << "static std::unique_ptr<" << mangle(module); f << template_params(module, /*is_decl=*/false) << "> "; f << "create(std::string name, metadata_map parameters, metadata_map attributes);\n"; @@ -2457,8 +2463,18 @@ struct CxxrtlWorker { f << indent << "};\n"; f << "\n"; f << indent << "void reset() override;\n"; + f << "\n"; f << indent << "bool eval() override;\n"; - f << indent << "bool commit() override;\n"; + f << "\n"; + f << indent << "template\n"; + f << indent << "bool commit(ObserverT &observer) {\n"; + dump_commit_method(module); + f << indent << "}\n"; + f << "\n"; + f << indent << "bool commit() override {\n"; + f << indent << indent << "null_observer observer;\n"; + f << indent << indent << "return commit<>(observer);\n"; + f << indent << "}\n"; if (debug_info) { if (debug_eval) { f << "\n"; @@ -2490,24 +2506,20 @@ struct CxxrtlWorker { f << indent << "bool " << mangle(module) << "::eval() {\n"; dump_eval_method(module); f << indent << "}\n"; - f << "\n"; - f << indent << "bool " << mangle(module) << "::commit() {\n"; - dump_commit_method(module); - f << indent << "}\n"; - f << "\n"; if (debug_info) { if (debug_eval) { + f << "\n"; f << indent << "void " << mangle(module) << "::debug_eval() {\n"; dump_debug_eval_method(module); f << indent << "}\n"; - f << "\n"; } + f << "\n"; f << indent << "CXXRTL_EXTREMELY_COLD\n"; f << indent << "void " << mangle(module) << "::debug_info(debug_items &items, std::string path) {\n"; dump_debug_info_method(module); f << indent << "}\n"; - f << "\n"; } + f << "\n"; } void dump_design(RTLIL::Design *design) @@ -3267,6 +3279,8 @@ struct CxxrtlBackend : public Backend { log(" wire<8> p_o_data;\n"); log("\n"); log(" bool eval() override;\n"); + log(" template\n"); + log(" bool commit(ObserverT &observer);\n"); log(" bool commit() override;\n"); log("\n"); log(" static std::unique_ptr\n"); diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index 78dbf37072d..53fb3dbc010 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -841,6 +841,29 @@ std::ostream &operator<<(std::ostream &os, const value_formatted &vf) return os; } +// An object that can be passed to a `commit()` method in order to produce a replay log of every +// state change in the simulation. +struct observer { + // Called when a `commit()` method for a wire is about to update the `chunks` chunks at `base` + // with `chunks` chunks at `value` that have a different bit pattern. It is guaranteed that + // `chunks` is equal to the wire chunk count and `base` points to the first chunk. + virtual void on_commit(size_t chunks, const chunk_t *base, const chunk_t *value) = 0; + + // Called when a `commit()` method for a memory is about to update the `chunks` chunks at + // `&base[chunks * index]` with `chunks` chunks at `value` that have a different bit pattern. + // It is guaranteed that `chunks` covers is equal to the memory element chunk count and `base` + // points to the first chunk of the first element of the memory. + virtual void on_commit(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) = 0; +}; + +// The `null_observer` class has the same interface as `observer`, but has no invocation overhead, +// since its methods are final and have no implementation. This allows the observer feature to be +// zero-cost when not in use. +struct null_observer final: observer { + void on_commit(size_t chunks, const chunk_t *base, const chunk_t *value) override {} + void on_commit(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) override {} +}; + template struct wire { static constexpr size_t bits = Bits; @@ -875,8 +898,14 @@ struct wire { next.template set(other); } - bool commit() { + // This method intentionally takes a mandatory argument (to make it more difficult to misuse in + // black box implementations, leading to missed observer events). It is generic over its argument + // to make sure the `on_commit` call is devirtualized. This is somewhat awkward but lets us keep + // a single implementation for both this method and the one in `memory`. + template + bool commit(ObserverT &observer) { if (curr != next) { + observer.on_commit(curr.chunks, curr.data, next.data); curr = next; return true; } @@ -950,12 +979,17 @@ struct memory { write { index, val, mask, priority }); } - bool commit() { + // See the note for `wire::commit()`. + template + bool commit(ObserverT &observer) { bool changed = false; for (const write &entry : write_queue) { value elem = data[entry.index]; elem = elem.update(entry.val, entry.mask); - changed |= (data[entry.index] != elem); + if (data[entry.index] != elem) { + observer.on_commit(value::chunks, data[0].data, elem.data, entry.index); + changed |= true; + } data[entry.index] = elem; } write_queue.clear(); From f9dc1a2184720748c36042792888f6e82867f285 Mon Sep 17 00:00:00 2001 From: Catherine Date: Fri, 5 Jan 2024 20:18:36 +0000 Subject: [PATCH 20/70] cxxrtl: fix comment wording. NFC --- backends/cxxrtl/runtime/cxxrtl/cxxrtl.h | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index 53fb3dbc010..183fbb2c7c7 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -841,24 +841,22 @@ std::ostream &operator<<(std::ostream &os, const value_formatted &vf) return os; } -// An object that can be passed to a `commit()` method in order to produce a replay log of every -// state change in the simulation. +// An object that can be passed to a `commit()` method in order to produce a replay log of every state change in +// the simulation. struct observer { - // Called when a `commit()` method for a wire is about to update the `chunks` chunks at `base` - // with `chunks` chunks at `value` that have a different bit pattern. It is guaranteed that - // `chunks` is equal to the wire chunk count and `base` points to the first chunk. + // Called when the `commit()` method for a wire is about to update the `chunks` chunks at `base` with `chunks` chunks + // at `value` that have a different bit pattern. It is guaranteed that `chunks` is equal to the wire chunk count and + // `base` points to the first chunk. virtual void on_commit(size_t chunks, const chunk_t *base, const chunk_t *value) = 0; - // Called when a `commit()` method for a memory is about to update the `chunks` chunks at - // `&base[chunks * index]` with `chunks` chunks at `value` that have a different bit pattern. - // It is guaranteed that `chunks` covers is equal to the memory element chunk count and `base` - // points to the first chunk of the first element of the memory. + // Called when the `commit()` method for a memory is about to update the `chunks` chunks at `&base[chunks * index]` + // with `chunks` chunks at `value` that have a different bit pattern. It is guaranteed that `chunks` is equal to + // the memory element chunk count and `base` points to the first chunk of the first element of the memory. virtual void on_commit(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) = 0; }; -// The `null_observer` class has the same interface as `observer`, but has no invocation overhead, -// since its methods are final and have no implementation. This allows the observer feature to be -// zero-cost when not in use. +// The `null_observer` class has the same interface as `observer`, but has no invocation overhead, since its methods +// are final and have no implementation. This allows the observer feature to be zero-cost when not in use. struct null_observer final: observer { void on_commit(size_t chunks, const chunk_t *base, const chunk_t *value) override {} void on_commit(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) override {} From 30b795601c514cad22952a199d4cae2c735be595 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 6 Jan 2024 00:16:22 +0000 Subject: [PATCH 21/70] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 60d67d05a3d..3da89690f39 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LDLIBS += -lrt endif endif -YOSYS_VER := 0.36+61 +YOSYS_VER := 0.36+67 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo From 82fca5030974f77a63b2cbd197a63ebadfeb9671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Sat, 6 Jan 2024 16:44:36 +0100 Subject: [PATCH 22/70] write_verilog: Handle edge case with non-pruned processes This change only matters for processes that weren't processed by `proc_rmdead` for which follow-up cases after a default case are treated differently in Verilog and RTLIL semantics. --- backends/verilog/verilog_backend.cc | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/backends/verilog/verilog_backend.cc b/backends/verilog/verilog_backend.cc index 735672a43b4..fd70695d314 100644 --- a/backends/verilog/verilog_backend.cc +++ b/backends/verilog/verilog_backend.cc @@ -1988,12 +1988,10 @@ void dump_proc_switch(std::ostream &f, std::string indent, RTLIL::SwitchRule *sw dump_sigspec(f, sw->signal); f << stringf(")\n"); - bool got_default = false; for (auto it = sw->cases.begin(); it != sw->cases.end(); ++it) { + bool got_default = false; dump_attributes(f, indent + " ", (*it)->attributes, '\n', /*modattr=*/false, /*regattr=*/false, /*as_comment=*/true); if ((*it)->compare.size() == 0) { - if (got_default) - continue; f << stringf("%s default", indent.c_str()); got_default = true; } else { @@ -2006,6 +2004,14 @@ void dump_proc_switch(std::ostream &f, std::string indent, RTLIL::SwitchRule *sw } f << stringf(":\n"); dump_case_body(f, indent + " ", *it); + + if (got_default) { + // If we followed up the default with more cases the Verilog + // semantics would be to match those *before* the default, but + // the RTLIL semantics are to match those *after* the default + // (so they can never be selected). Exit now. + break; + } } if (sw->cases.empty()) { From 22370ad21e0308a912f0a443d4ae4064beeb9411 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 9 Jan 2024 00:16:54 +0000 Subject: [PATCH 23/70] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3da89690f39..946c323e7d0 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LDLIBS += -lrt endif endif -YOSYS_VER := 0.36+67 +YOSYS_VER := 0.36+72 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo From c045c9a5c9ecff143fcac7095c93c0c1e8a72588 Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Tue, 9 Jan 2024 10:58:31 +0100 Subject: [PATCH 24/70] Update macOS to Ventura --- .github/workflows/test-macos.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-macos.yml b/.github/workflows/test-macos.yml index 04845723467..2b48b725237 100644 --- a/.github/workflows/test-macos.yml +++ b/.github/workflows/test-macos.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: os: - - { id: macos-11, name: 'Big Sur' } + - { id: macos-13, name: 'Ventura' } cpp_std: - 'c++11' - 'c++17' From 5aaf1f1d398686a28192d5488da4337303a329df Mon Sep 17 00:00:00 2001 From: Catherine Date: Fri, 5 Jan 2024 21:31:08 +0000 Subject: [PATCH 25/70] cxxrtl: implement `value.get()` and `value.set()` for signed types. --- backends/cxxrtl/runtime/cxxrtl/cxxrtl.h | 26 +++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index 183fbb2c7c7..3f824722665 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -145,7 +146,7 @@ struct value : public expr_base> { // These functions ensure that a conversion is never out of range, and should be always used, if at all // possible, instead of direct manipulation of the `data` member. For very large types, .slice() and // .concat() can be used to split them into more manageable parts. - template + template::value, int>::type = 0> CXXRTL_ALWAYS_INLINE IntegerT get() const { static_assert(std::numeric_limits::is_integer && !std::numeric_limits::is_signed, @@ -158,15 +159,32 @@ struct value : public expr_base> { return result; } - template + template::value, int>::type = 0> CXXRTL_ALWAYS_INLINE - void set(IntegerT other) { + IntegerT get() const { + auto unsigned_result = get::type>(); + IntegerT result; + memcpy(&result, &unsigned_result, sizeof(IntegerT)); + return result; + } + + template::value, int>::type = 0> + CXXRTL_ALWAYS_INLINE + void set(IntegerT value) { static_assert(std::numeric_limits::is_integer && !std::numeric_limits::is_signed, "set() requires T to be an unsigned integral type"); static_assert(std::numeric_limits::digits >= Bits, "set() requires the value to be at least as wide as T is"); for (size_t n = 0; n < chunks; n++) - data[n] = (other >> (n * chunk::bits)) & chunk::mask; + data[n] = (value >> (n * chunk::bits)) & chunk::mask; + } + + template::value, int>::type = 0> + CXXRTL_ALWAYS_INLINE + void set(IntegerT value) { + typename std::make_unsigned::type unsigned_value; + memcpy(&unsigned_value, &value, sizeof(IntegerT)); + set(unsigned_value); } // Operations with compile-time parameters. From a59d477098f4b533ac7cc95abdacb3943792334c Mon Sep 17 00:00:00 2001 From: Catherine Date: Fri, 5 Jan 2024 20:09:49 +0000 Subject: [PATCH 26/70] cxxrtl: improve robustness of `cxxrtl::time`. Avoid overflow during conversion for any representable raw value. --- backends/cxxrtl/runtime/cxxrtl/cxxrtl_time.h | 36 +++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl_time.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl_time.h index 51f59321e2d..f37c2b65640 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl_time.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl_time.h @@ -26,17 +26,19 @@ namespace cxxrtl { -// A timestamp or a difference in time, stored as a 96-bit number of femtoseconds (10e-15 s). The dynamic range and -// resolution of this format can represent any VCD timestamp within 136 years, without the need for a timescale. +// A timestamp or a difference in time, stored as a 96-bit number of femtoseconds (10e-15 s). The range and resolution +// of this format can represent any VCD timestamp within approx. ±1255321.2 years, without the need for a timescale. class time { public: static constexpr size_t bits = 96; // 3 chunks private: + static constexpr size_t resolution_digits = 15; + + static_assert(sizeof(chunk_t) == 4, "a chunk is expected to be 32-bit"); static constexpr value resolution = value { chunk_t(1000000000000000ull & 0xffffffffull), chunk_t(1000000000000000ull >> 32), 0u }; - static constexpr size_t resolution_digits = 15; // Signed number of femtoseconds from the beginning of time. value raw; @@ -51,11 +53,11 @@ class time { return time(value { 0xffffffffu, 0xffffffffu, 0x7fffffffu }); } - time(int32_t secs, int64_t femtos) { - value<32> secs_val; - secs_val.set((uint32_t)secs); + time(int64_t secs, int64_t femtos) { + value<64> secs_val; + secs_val.set(secs); value<64> femtos_val; - femtos_val.set((uint64_t)femtos); + femtos_val.set(femtos); raw = secs_val.sext().mul(resolution).add(femtos_val.sext()); } @@ -68,14 +70,14 @@ class time { return raw.is_neg(); } - // Extracts the absolute number of whole seconds. Negative if the value is negative. - int32_t secs() const { - return raw.sdivmod(resolution).first.trunc<32>().get(); + // Extracts the number of whole seconds. Negative if the value is negative. + int64_t secs() const { + return raw.sdivmod(resolution).first.trunc<64>().get(); } - // Extracts the absolute number of femtoseconds in the fractional second. Negative if the value is negative. + // Extracts the number of femtoseconds in the fractional second. Negative if the value is negative. int64_t femtos() const { - return raw.sdivmod(resolution).second.trunc<64>().get(); + return raw.sdivmod(resolution).second.trunc<64>().get(); } bool operator==(const time &other) const { @@ -125,10 +127,10 @@ class time { } operator std::string() const { - char buf[38]; // len(f"-{2**64}.{10**15-1}") + 1 == 38 - int32_t secs = this->secs(); + char buf[48]; // x=2**95; len(f"-{x/1_000_000_000_000_000}.{x^1_000_000_000_000_000}") == 48 + int64_t secs = this->secs(); int64_t femtos = this->femtos(); - snprintf(buf, sizeof(buf), "%s%" PRIi32 ".%015" PRIi64, + snprintf(buf, sizeof(buf), "%s%" PRIi64 ".%015" PRIi64, is_negative() ? "-" : "", secs >= 0 ? secs : -secs, femtos >= 0 ? femtos : -femtos); return buf; } @@ -143,7 +145,7 @@ class time { parse_fractional, } state = parse_sign_opt; bool negative = false; - int32_t integral = 0; + int64_t integral = 0; int64_t fractional = 0; size_t frac_digits = 0; for (auto chr : str) { @@ -199,7 +201,7 @@ std::ostream &operator<<(std::ostream &os, const time &val) { namespace time_literals { time operator""_s(unsigned long long seconds) { - return time { (int32_t)seconds, 0 }; + return time { (int64_t)seconds, 0 }; } time operator""_ms(unsigned long long milliseconds) { From f6e36f0e540bedc82d98cfda6560ab0ee04e887e Mon Sep 17 00:00:00 2001 From: Catherine Date: Fri, 1 Dec 2023 16:01:28 +0000 Subject: [PATCH 27/70] cxxrtl: implement a generic record/replay interface. This commit adds a reader/writer implementation for a file format optimized for fast, single-pass storage and retrieval of design state changes, as well as a recorder/replayer that integrate with the eval and commit simulation steps to create replay logs and reproduce them later. This feature makes it possible to run a simulation once, recording the stimulus as well as changes to the registers, and navigate to a past time point in the simulation later without rerunning it. Both the changes in inputs (stimulus) and changes in state are saved so that navigation does not require calling `eval()` or `commit()`; only a series of memory copy operations. On a representative example of a SoC netlist, saving the replay log while simulating it takes 150% of the time it would take to simulate the same design without logging, which is a much lower overhead than writing an equivalent full view (including memories) VCD waveform dump. The replay log is also several times smaller than the VCD dump, and more space savings are available as low hanging fruit. Replaying the log has not been optimized and currently takes about the same time as running the simulation in first place. However, it is still useful since it provides fast navigation to an arbitrary time point, something that rerunning the simulation does not allow for. The current file format should be considered preliminary. It is not very space-efficient, and my testing shows that a lot of time is spent in the write() syscall in the kernel. Most likely, compression and/or writing in another thread could improve performance by 10-20%. This may be done at a later time. --- .../cxxrtl/runtime/cxxrtl/cxxrtl_replay.h | 785 ++++++++++++++++++ 1 file changed, 785 insertions(+) create mode 100644 backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h new file mode 100644 index 00000000000..94f59bb0d9b --- /dev/null +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h @@ -0,0 +1,785 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2023 Catherine + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef CXXRTL_REPLAY_H +#define CXXRTL_REPLAY_H + +#if !defined(WIN32) +#include +#define O_BINARY 0 +#else +#include +#endif + +#include +#include +#include +#include +#include + +#include +#include + +// Theory of operation +// =================== +// +// Log format +// ---------- +// +// The replay log is a simple data format based on a sequence of 32-bit words. The following BNF-like grammar describes +// enough detail to understand the overall structure of the log data and be able to read hex dumps. For a greater +// degree of detail see the source code. The format is considered fully internal to CXXRTL and is subject to change +// without notice. +// +// ::= + +// ::= 0x52585843 0x00004c54 +// ::= * +// ::= * +// ::= 0xc0000000 ... +// ::= 0xc0000001 ... +// ::= 0x0??????? + | 0x1??????? + | 0x2??????? | 0x3??????? +// , ::= 0x???????? +// ::= 0xFFFFFFFF +// +// The replay log contains sample data, however, it does not cover the entire design. Rather, it only contains sample +// data for the subset of debug items containing _design state_: inputs and registers/latches. This keeps its size to +// a minimum, and recording speed to a maximum. The player samples any missing data by setting the design state items +// to the same values they had during recording, and re-evaluating the design. +// +// Limits +// ------ +// +// The log may contain: +// +// * Up to 2**28-1 debug items containing design state. +// * Up to 2**32 chunks per debug item. +// * Up to 2**32 rows per memory. +// * Up to 2**32 samples. +// +// Of these limits, the last two are most likely to be eventually exceeded by practical recordings. However, other +// performance considerations will likely limit the size of such practical recordings first, so the log data format +// will undergo a breaking change at that point. +// +// Operations +// ---------- +// +// As suggested by the name "replay log", this format is designed for recording (writing) once and playing (reading) +// many times afterwards, such that reading the format can be done linearly and quickly. The log format is designed to +// support three primary read operations: +// +// 1. Initialization +// 2. Rewinding (to time T) +// 3. Replaying (for N samples) +// +// During initialization, the player establishes the mapping between debug item names and their 28-bit identifiers in +// the log. It is done once. +// +// During rewinding, the player begins reading at the latest non-incremental sample that still lies before the requested +// sample time. It continues reading incremental samples after that point until it reaches the requested sample time. +// This process is very cheap as the design is not evaluated; it is essentially a (convoluted) memory copy operation. +// +// During replaying, the player evaluates the design at the current time, which causes all debug items to assume +// the values they had before recording. This process is expensive. Once done, the player advances to the next state +// by reading the next (complete or incremental) sample, as above. Since a range of samples is replayed, this process +// is repeated several times in a row. +// +// In principle, when replaying, the player could only read the state of the inputs and the time delta and use a normal +// eval/commit loop to progress the simulation, which is fully deterministic so its calculated design state should be +// exactly the same as the recorded design state. In practice, it is both faster and more reliable (in presence of e.g. +// user-defined black boxes) to read the recorded values instead of calculating them. +// +// Note: The operations described above are conceptual and do not correspond exactly to methods on `cxxrtl::player`. +// The `cxxrtl::player::replay()` method does not evaluate the design. This is so that delta cycles could be ignored +// if they are not of interest while replaying. + +namespace cxxrtl { + +// A spool stores CXXRTL design state changes in a file. +class spool { +public: + // Unique pointer to a specific sample within a replay log. (Timestamps are not unique.) + typedef uint32_t pointer_t; + + // Numeric identifier assigned to a debug item within a replay log. Range limited to [1, MAXIMUM_IDENT]. + typedef uint32_t ident_t; + + static constexpr uint16_t VERSION = 0x0400; + + static constexpr uint64_t HEADER_MAGIC = 0x00004c5452585843; + static constexpr uint64_t VERSION_MASK = 0xffff000000000000; + + static constexpr uint32_t PACKET_DEFINE = 0xc0000000; + + static constexpr uint32_t PACKET_SAMPLE = 0xc0000001; + enum sample_flag : uint32_t { + EMPTY = 0, + INCREMENTAL = 1, + }; + + static constexpr uint32_t MAXIMUM_IDENT = 0x0fffffff; + static constexpr uint32_t CHANGE_MASK = 0x30000000; + + static constexpr uint32_t PACKET_CHANGE = 0x00000000/* | ident */; + static constexpr uint32_t PACKET_CHANGEI = 0x10000000/* | ident */; + static constexpr uint32_t PACKET_CHANGEL = 0x20000000/* | ident */; + static constexpr uint32_t PACKET_CHANGEH = 0x30000000/* | ident */; + + static constexpr uint32_t PACKET_END = 0xffffffff; + + // Writing spools. + + class writer { + int fd; + size_t position; + std::vector buffer; + + // These functions aren't overloaded because of implicit numeric conversions. + + void emit_word(uint32_t word) { + if (position + 1 == buffer.size()) + flush(); + buffer[position++] = word; + } + + void emit_dword(uint64_t dword) { + emit_word(dword >> 0); + emit_word(dword >> 32); + } + + void emit_ident(ident_t ident) { + assert(ident <= MAXIMUM_IDENT); + emit_word(ident); + } + + void emit_size(size_t size) { + assert(size <= std::numeric_limits::max()); + emit_word(size); + } + + // Same implementation as `emit_size()`, different declared intent. + void emit_index(size_t index) { + assert(index <= std::numeric_limits::max()); + emit_word(index); + } + + void emit_string(std::string str) { + // Align to a word boundary, and add at least one terminating \0. + str.resize(str.size() + (sizeof(uint32_t) - (str.size() + sizeof(uint32_t)) % sizeof(uint32_t))); + for (size_t index = 0; index < str.size(); index += sizeof(uint32_t)) { + uint32_t word; + memcpy(&word, &str[index], sizeof(uint32_t)); + emit_word(word); + } + } + + void emit_time(const time ×tamp) { + const value &raw_timestamp(timestamp); + emit_word(raw_timestamp.data[0]); + emit_word(raw_timestamp.data[1]); + emit_word(raw_timestamp.data[2]); + } + + public: + // Creates a writer, and transfers ownership of `fd`, which must be open for appending. + // + // The buffer size is currently fixed to a "reasonably large" size, determined empirically by measuring writer + // performance on a representative design; large but not so large it would e.g. cause address space exhaustion + // on 32-bit platforms. + writer(spool &spool) : fd(spool.take_write()), position(0), buffer(32 * 1024 * 1024) { + assert(fd != -1); +#if !defined(WIN32) + int result = ftruncate(fd, 0); +#else + int result = _chsize_s(fd, 0); +#endif + assert(result == 0); + } + + writer(writer &&moved) : fd(moved.fd), position(moved.position), buffer(moved.buffer) { + moved.fd = -1; + moved.position = 0; + } + + writer(const writer &) = delete; + writer &operator=(const writer &) = delete; + + // Both write() calls and fwrite() calls are too expensive to perform implicitly. The API consumer must determine + // the optimal time to flush the writer and do that explicitly for best performance. + void flush() { + assert(fd != -1); + size_t data_size = position * sizeof(uint32_t); + size_t data_written = write(fd, buffer.data(), data_size); + assert(data_size == data_written); + position = 0; + } + + ~writer() { + if (fd != -1) { + flush(); + close(fd); + } + } + + void write_magic() { + // `CXXRTL` followed by version in binary. This header will read backwards on big-endian machines, which allows + // detection of this case, both visually and programmatically. + emit_dword(((uint64_t)VERSION << 48) | HEADER_MAGIC); + } + + void write_define(ident_t ident, const std::string &name, size_t part_index, size_t chunks, size_t depth) { + emit_word(PACKET_DEFINE); + emit_ident(ident); + emit_string(name); + emit_index(part_index); + emit_size(chunks); + emit_size(depth); + } + + void write_sample(bool incremental, pointer_t pointer, const time ×tamp) { + uint32_t flags = (incremental ? sample_flag::INCREMENTAL : 0); + emit_word(PACKET_SAMPLE); + emit_word(flags); + emit_word(pointer); + emit_time(timestamp); + } + + void write_change(ident_t ident, size_t chunks, const chunk_t *data) { + assert(ident <= MAXIMUM_IDENT); + + if (chunks == 1 && *data == 0) { + emit_word(PACKET_CHANGEL | ident); + } else if (chunks == 1 && *data == 1) { + emit_word(PACKET_CHANGEH | ident); + } else { + emit_word(PACKET_CHANGE | ident); + for (size_t offset = 0; offset < chunks; offset++) + emit_word(data[offset]); + } + } + + void write_change(ident_t ident, size_t chunks, const chunk_t *data, size_t index) { + assert(ident <= MAXIMUM_IDENT); + + emit_word(PACKET_CHANGEI | ident); + emit_index(index); + for (size_t offset = 0; offset < chunks; offset++) + emit_word(data[offset]); + } + + void write_end() { + emit_word(PACKET_END); + } + }; + + // Reading spools. + + class reader { + FILE *f; + + uint32_t absorb_word() { + // If we're at end of file, `fread` will not write to `word`, and `PACKET_END` will be returned. + uint32_t word = PACKET_END; + fread(&word, sizeof(word), 1, f); + return word; + } + + uint64_t absorb_dword() { + uint32_t lo = absorb_word(); + uint32_t hi = absorb_word(); + return ((uint64_t)hi << 32) | lo; + } + + ident_t absorb_ident() { + ident_t ident = absorb_word(); + assert(ident <= MAXIMUM_IDENT); + return ident; + } + + size_t absorb_size() { + return absorb_word(); + } + + size_t absorb_index() { + return absorb_word(); + } + + std::string absorb_string() { + std::string str; + do { + size_t end = str.size(); + str.resize(end + 4); + uint32_t word = absorb_word(); + memcpy(&str[end], &word, sizeof(uint32_t)); + } while (str.back() != '\0'); + // Strings have no embedded zeroes besides the terminating one(s). + return str.substr(0, str.find('\0')); + } + + time absorb_time() { + value raw_timestamp; + raw_timestamp.data[0] = absorb_word(); + raw_timestamp.data[1] = absorb_word(); + raw_timestamp.data[2] = absorb_word(); + return time(raw_timestamp); + } + + public: + typedef uint64_t pos_t; + + // Creates a reader, and transfers ownership of `fd`, which must be open for reading. + reader(spool &spool) : f(fdopen(spool.take_read(), "r")) { + assert(f != nullptr); + } + + reader(reader &&moved) : f(moved.f) { + moved.f = nullptr; + } + + reader(const reader &) = delete; + reader &operator=(const reader &) = delete; + + ~reader() { + if (f != nullptr) + fclose(f); + } + + pos_t position() { + return ftell(f); + } + + void rewind(pos_t position) { + fseek(f, position, SEEK_SET); + } + + void read_magic() { + uint64_t magic = absorb_dword(); + assert((magic & ~VERSION_MASK) == HEADER_MAGIC); + assert((magic >> 48) == VERSION); + } + + bool read_define(ident_t &ident, std::string &name, size_t &part_index, size_t &chunks, size_t &depth) { + uint32_t header = absorb_word(); + if (header == PACKET_END) + return false; + assert(header == PACKET_DEFINE); + ident = absorb_ident(); + name = absorb_string(); + part_index = absorb_index(); + chunks = absorb_size(); + depth = absorb_size(); + return true; + } + + bool read_sample(bool &incremental, pointer_t &pointer, time ×tamp) { + uint32_t header = absorb_word(); + if (header == PACKET_END) + return false; + assert(header == PACKET_SAMPLE); + uint32_t flags = absorb_word(); + incremental = (flags & sample_flag::INCREMENTAL); + pointer = absorb_word(); + timestamp = absorb_time(); + return true; + } + + bool read_change_header(uint32_t &header, ident_t &ident) { + header = absorb_word(); + if (header == PACKET_END) + return false; + assert((header & ~(CHANGE_MASK | MAXIMUM_IDENT)) == 0); + ident = header & MAXIMUM_IDENT; + return true; + } + + void read_change_data(uint32_t header, size_t chunks, size_t depth, chunk_t *data) { + uint32_t index = 0; + switch (header & CHANGE_MASK) { + case PACKET_CHANGEL: + *data = 0; + return; + case PACKET_CHANGEH: + *data = 1; + return; + case PACKET_CHANGE: + break; + case PACKET_CHANGEI: + index = absorb_word(); + assert(index < depth); + break; + default: + assert(false && "Unrecognized change packet"); + } + for (size_t offset = 0; offset < chunks; offset++) + data[chunks * index + offset] = absorb_word(); + } + }; + + // Opening spools. For certain uses of the record/replay mechanism, two distinct open files (two open files, i.e. + // two distinct file pointers, and not just file descriptors, which share the file pointer if duplicated) are used, + // for a reader and writer thread. This class manages the lifetime of the descriptors for these files. When only + // one of them is used, the other is closed harmlessly when the spool is destroyed. +private: + std::atomic writefd; + std::atomic readfd; + +public: + spool(const std::string &filename) + : writefd(open(filename.c_str(), O_CREAT|O_BINARY|O_WRONLY|O_APPEND, 0644)), + readfd(open(filename.c_str(), O_BINARY|O_RDONLY)) { + assert(writefd.load() != -1 && readfd.load() != -1); + } + + spool(spool &&moved) : writefd(moved.writefd.exchange(-1)), readfd(moved.readfd.exchange(-1)) {} + + spool(const spool &) = delete; + spool &operator=(const spool &) = delete; + + ~spool() { + if (int fd = writefd.exchange(-1)) + close(fd); + if (int fd = readfd.exchange(-1)) + close(fd); + } + + // Atomically acquire a write file descriptor for the spool. Can be called once, and will return -1 the next time + // it is called. Thread-safe. + int take_write() { + return writefd.exchange(-1); + } + + // Atomically acquire a read file descriptor for the spool. Can be called once, and will return -1 the next time + // it is called. Thread-safe. + int take_read() { + return readfd.exchange(-1); + } +}; + +// A CXXRTL recorder samples design state, producing complete or incremental updates, and writes them to a spool. +class recorder { + struct variable { + spool::ident_t ident; /* <= spool::MAXIMUM_IDENT */ + size_t chunks; + size_t depth; /* == 1 for wires */ + chunk_t *curr; + bool memory; + }; + + spool::writer writer; + std::vector variables; + std::vector inputs; // values of inputs must be recorded explicitly, as their changes are not observed + std::unordered_map ident_lookup; + bool streaming = false; // whether variable definitions have been written + spool::pointer_t pointer = 0; + time timestamp; + +public: + template + recorder(Args &&...args) : writer(std::forward(args)...) {} + + void start(module &module) { + debug_items items; + module.debug_info(items); + start(items); + } + + void start(const debug_items &items) { + assert(!streaming); + + writer.write_magic(); + for (auto item : items.table) + for (size_t part_index = 0; part_index < item.second.size(); part_index++) { + auto &part = item.second[part_index]; + if ((part.flags & debug_item::INPUT) || (part.flags & debug_item::DRIVEN_SYNC) || + (part.type == debug_item::MEMORY)) { + variable var; + var.ident = variables.size() + 1; + var.chunks = (part.width + sizeof(chunk_t) * 8 - 1) / (sizeof(chunk_t) * 8); + var.depth = part.depth; + var.curr = part.curr; + var.memory = (part.type == debug_item::MEMORY); + ident_lookup[var.curr] = var.ident; + + assert(variables.size() < spool::MAXIMUM_IDENT); + if (part.flags & debug_item::INPUT) + inputs.push_back(variables.size()); + variables.push_back(var); + + writer.write_define(var.ident, item.first, part_index, var.chunks, var.depth); + } + } + writer.write_end(); + streaming = true; + } + + const time &latest_time() { + return timestamp; + } + + const time &advance_time(const time &delta) { + assert(!delta.is_negative()); + timestamp += delta; + return timestamp; + } + + void record_complete() { + assert(streaming); + + writer.write_sample(/*incremental=*/false, pointer++, timestamp); + for (auto var : variables) { + assert(var.ident != 0); + if (!var.memory) + writer.write_change(var.ident, var.chunks, var.curr); + else + for (size_t index = 0; index < var.depth; index++) + writer.write_change(var.ident, var.chunks, &var.curr[var.chunks * index], index); + } + writer.write_end(); + } + + // This function is generic over ModuleT to encourage observer callbacks to be inlined into the commit function. + template + bool record_incremental(ModuleT &module) { + assert(streaming); + + struct : public observer { + std::unordered_map *ident_lookup; + spool::writer *writer; + + CXXRTL_ALWAYS_INLINE + void on_commit(size_t chunks, const chunk_t *base, const chunk_t *value) override { + writer->write_change(ident_lookup->at(base), chunks, value); + } + + CXXRTL_ALWAYS_INLINE + void on_commit(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) override { + writer->write_change(ident_lookup->at(base), chunks, value, index); + } + } record_observer; + record_observer.ident_lookup = &ident_lookup; + record_observer.writer = &writer; + + writer.write_sample(/*incremental=*/true, pointer++, timestamp); + for (auto input_index : inputs) { + variable &var = variables.at(input_index); + assert(!var.memory); + writer.write_change(var.ident, var.chunks, var.curr); + } + bool changed = module.commit(record_observer); + writer.write_end(); + return changed; + } + + void flush() { + writer.flush(); + } +}; + +// A CXXRTL player reads samples from a spool, and changes the design state accordingly. To start reading samples, +// a spool must have been initialized: the recorder must have been started and an initial complete sample must have +// been written. +class player { + struct variable { + size_t chunks; + size_t depth; /* == 1 for wires */ + chunk_t *curr; + }; + + spool::reader reader; + std::unordered_map variables; + bool streaming = false; // whether variable definitions have been read + bool initialized = false; // whether a sample has ever been read + spool::pointer_t pointer = 0; + time timestamp; + + std::map> index_by_pointer; + std::map> index_by_timestamp; + + bool peek_sample(spool::pointer_t &pointer, time ×tamp) { + bool incremental; + auto position = reader.position(); + bool success = reader.read_sample(incremental, pointer, timestamp); + reader.rewind(position); + return success; + } + +public: + template + player(Args &&...args) : reader(std::forward(args)...) {} + + void start(module &module) { + debug_items items; + module.debug_info(items); + start(items); + } + + void start(const debug_items &items) { + assert(!streaming); + + reader.read_magic(); + while (true) { + spool::ident_t ident; + std::string name; + size_t part_index; + size_t chunks; + size_t depth; + if (!reader.read_define(ident, name, part_index, chunks, depth)) + break; + assert(variables.count(ident) == 0); + assert(items.count(name) != 0); + assert(part_index < items.count(name)); + + const debug_item &part = items.parts_at(name).at(part_index); + assert(chunks == (part.width + sizeof(chunk_t) * 8 - 1) / (sizeof(chunk_t) * 8)); + assert(depth == part.depth); + + variable &var = variables[ident]; + var.chunks = chunks; + var.depth = depth; + var.curr = part.curr; + } + assert(variables.size() > 0); + streaming = true; + + // Establish the initial state of the design. + initialized = replay(); + assert(initialized); + } + + // Returns the pointer of the current sample. + spool::pointer_t current_pointer() { + assert(initialized); + return pointer; + } + + // Returns the time of the current sample. + const time ¤t_time() { + assert(initialized); + return timestamp; + } + + // Returns `true` if there is a next sample to read, and sets `pointer` to its pointer if there is. + bool get_next_pointer(spool::pointer_t &pointer) { + assert(streaming); + time timestamp; + return peek_sample(pointer, timestamp); + } + + // Returns `true` if there is a next sample to read, and sets `timestamp` to its time if there is. + bool get_next_time(time ×tamp) { + assert(streaming); + uint32_t pointer; + return peek_sample(pointer, timestamp); + } + + // If this function returns `true`, then `current_pointer() == at_pointer`, and the module contains values that + // correspond to this pointer in the replay log. To obtain a valid pointer, call `current_pointer()`; while pointers + // are monotonically increasing for each consecutive sample, using arithmetic operations to create a new pointer is + // not allowed. + bool rewind_to(spool::pointer_t at_pointer) { + assert(initialized); + + // The pointers in the replay log start from one that is greater than `at_pointer`. In this case the pointer will + // never be reached. + assert(index_by_pointer.size() > 0); + if (at_pointer < index_by_pointer.rbegin()->first) + return false; + + // Find the last complete sample whose pointer is less than or equal to `at_pointer`. Note that the comparison + // function used here is `std::greater`, inverting the direction of `lower_bound`. + auto position_it = index_by_pointer.lower_bound(at_pointer); + assert(position_it != index_by_pointer.end()); + reader.rewind(position_it->second); + + // Replay samples until eventually arriving to `at_pointer` or encountering end of file. + while(replay()) { + if (pointer == at_pointer) + return true; + } + return false; + } + + // If this function returns `true`, then `current_time() <= at_or_before_timestamp`, and the module contains values + // that correspond to `current_time()` in the replay log. If `current_time() == at_or_before_timestamp` and there + // are several consecutive samples with the same time, the module contains values that correspond to the first of + // these samples. + bool rewind_to_or_before(const time &at_or_before_timestamp) { + assert(initialized); + + // The timestamps in the replay log start from one that is greater than `at_or_before_timestamp`. In this case + // the timestamp will never be reached. Otherwise, this function will always succeed. + assert(index_by_timestamp.size() > 0); + if (at_or_before_timestamp < index_by_timestamp.rbegin()->first) + return false; + + // Find the last complete sample whose timestamp is less than or equal to `at_or_before_timestamp`. Note that + // the comparison function used here is `std::greater`, inverting the direction of `lower_bound`. + auto position_it = index_by_timestamp.lower_bound(at_or_before_timestamp); + assert(position_it != index_by_timestamp.end()); + reader.rewind(position_it->second); + + // Replay samples until eventually arriving to or past `at_or_before_timestamp` or encountering end of file. + while (replay()) { + if (timestamp == at_or_before_timestamp) + break; + + time next_timestamp; + if (!get_next_time(next_timestamp)) + break; + if (next_timestamp > at_or_before_timestamp) + break; + } + return true; + } + + // If this function returns `true`, then `current_pointer()` and `current_time()` are updated for the next sample + // and the module now contains values that correspond to that sample. If it returns `false`, there was no next sample + // to read. + bool replay() { + assert(streaming); + + bool incremental; + auto position = reader.position(); + if (!reader.read_sample(incremental, pointer, timestamp)) + return false; + + // The very first sample that is read must be a complete sample. This is required for the rewind functions to work. + assert(initialized || !incremental); + + // It is possible (though not very useful) to have several complete samples with the same timestamp in a row. + // Ensure that we associate the timestamp with the position of the first such complete sample. (This condition + // works because the player never jumps over a sample.) + if (!incremental && !index_by_pointer.count(pointer)) { + assert(!index_by_timestamp.count(timestamp)); + index_by_pointer[pointer] = position; + index_by_timestamp[timestamp] = position; + } + + uint32_t header; + spool::ident_t ident; + variable var; + while (reader.read_change_header(header, ident)) { + variable &var = variables.at(ident); + reader.read_change_data(header, var.chunks, var.depth, var.curr); + } + return true; + } +}; + +} + +#endif From bc9206f0f58b04804716cbffe400ecd04535a614 Mon Sep 17 00:00:00 2001 From: Catherine Date: Thu, 21 Dec 2023 02:02:39 +0000 Subject: [PATCH 28/70] write_verilog: emit `casez` as `if/elif/else` whenever possible. --- backends/verilog/verilog_backend.cc | 87 +++++++++++++++++++++++------ 1 file changed, 70 insertions(+), 17 deletions(-) diff --git a/backends/verilog/verilog_backend.cc b/backends/verilog/verilog_backend.cc index fd70695d314..9ff2c5c8606 100644 --- a/backends/verilog/verilog_backend.cc +++ b/backends/verilog/verilog_backend.cc @@ -376,7 +376,7 @@ void dump_sigspec(std::ostream &f, const RTLIL::SigSpec &sig) } } -void dump_attributes(std::ostream &f, std::string indent, dict &attributes, char term = '\n', bool modattr = false, bool regattr = false, bool as_comment = false) +void dump_attributes(std::ostream &f, std::string indent, dict &attributes, std::string term = "\n", bool modattr = false, bool regattr = false, bool as_comment = false) { if (noattr) return; @@ -392,13 +392,13 @@ void dump_attributes(std::ostream &f, std::string indent, dictsecond, -1, 0, false, as_comment); - f << stringf(" %s%c", as_comment ? "*/" : "*)", term); + f << stringf(" %s%s", as_comment ? "*/" : "*)", term.c_str()); } } void dump_wire(std::ostream &f, std::string indent, RTLIL::Wire *wire) { - dump_attributes(f, indent, wire->attributes, '\n', /*modattr=*/false, /*regattr=*/reg_wires.count(wire->name)); + dump_attributes(f, indent, wire->attributes, "\n", /*modattr=*/false, /*regattr=*/reg_wires.count(wire->name)); #if 0 if (wire->port_input && !wire->port_output) f << stringf("%s" "input %s", indent.c_str(), reg_wires.count(wire->name) ? "reg " : ""); @@ -989,7 +989,7 @@ void dump_cell_expr_uniop(std::ostream &f, std::string indent, RTLIL::Cell *cell f << stringf("%s" "assign ", indent.c_str()); dump_sigspec(f, cell->getPort(ID::Y)); f << stringf(" = %s ", op.c_str()); - dump_attributes(f, "", cell->attributes, ' '); + dump_attributes(f, "", cell->attributes, " "); dump_cell_expr_port(f, cell, "A", true); f << stringf(";\n"); } @@ -1001,7 +1001,7 @@ void dump_cell_expr_binop(std::ostream &f, std::string indent, RTLIL::Cell *cell f << stringf(" = "); dump_cell_expr_port(f, cell, "A", true); f << stringf(" %s ", op.c_str()); - dump_attributes(f, "", cell->attributes, ' '); + dump_attributes(f, "", cell->attributes, " "); dump_cell_expr_port(f, cell, "B", true); f << stringf(";\n"); } @@ -1048,7 +1048,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) dump_sigspec(f, cell->getPort(ID::Y)); f << stringf(" = "); f << stringf("~"); - dump_attributes(f, "", cell->attributes, ' '); + dump_attributes(f, "", cell->attributes, " "); dump_cell_expr_port(f, cell, "A", false); f << stringf(";\n"); return true; @@ -1068,7 +1068,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) f << stringf("|"); if (cell->type.in(ID($_XOR_), ID($_XNOR_))) f << stringf("^"); - dump_attributes(f, "", cell->attributes, ' '); + dump_attributes(f, "", cell->attributes, " "); f << stringf(" "); if (cell->type.in(ID($_ANDNOT_), ID($_ORNOT_))) f << stringf("~("); @@ -1085,7 +1085,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) f << stringf(" = "); dump_cell_expr_port(f, cell, "S", false); f << stringf(" ? "); - dump_attributes(f, "", cell->attributes, ' '); + dump_attributes(f, "", cell->attributes, " "); dump_cell_expr_port(f, cell, "B", false); f << stringf(" : "); dump_cell_expr_port(f, cell, "A", false); @@ -1099,7 +1099,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) f << stringf(" = !("); dump_cell_expr_port(f, cell, "S", false); f << stringf(" ? "); - dump_attributes(f, "", cell->attributes, ' '); + dump_attributes(f, "", cell->attributes, " "); dump_cell_expr_port(f, cell, "B", false); f << stringf(" : "); dump_cell_expr_port(f, cell, "A", false); @@ -1115,7 +1115,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) f << stringf(cell->type == ID($_AOI3_) ? " & " : " | "); dump_cell_expr_port(f, cell, "B", false); f << stringf(cell->type == ID($_AOI3_) ? ") |" : ") &"); - dump_attributes(f, "", cell->attributes, ' '); + dump_attributes(f, "", cell->attributes, " "); f << stringf(" "); dump_cell_expr_port(f, cell, "C", false); f << stringf(");\n"); @@ -1130,7 +1130,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) f << stringf(cell->type == ID($_AOI4_) ? " & " : " | "); dump_cell_expr_port(f, cell, "B", false); f << stringf(cell->type == ID($_AOI4_) ? ") |" : ") &"); - dump_attributes(f, "", cell->attributes, ' '); + dump_attributes(f, "", cell->attributes, " "); f << stringf(" ("); dump_cell_expr_port(f, cell, "C", false); f << stringf(cell->type == ID($_AOI4_) ? " & " : " | "); @@ -1232,7 +1232,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) f << stringf("%s" "assign ", indent.c_str()); dump_sigspec(f, cell->getPort(ID::Y)); f << stringf(" = $signed(%s) / ", buf_num.c_str()); - dump_attributes(f, "", cell->attributes, ' '); + dump_attributes(f, "", cell->attributes, " "); f << stringf("$signed(%s);\n", buf_b.c_str()); return true; } else { @@ -1255,7 +1255,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) f << stringf("%s" "wire [%d:0] %s = ", indent.c_str(), GetSize(cell->getPort(ID::A))-1, temp_id.c_str()); dump_cell_expr_port(f, cell, "A", true); f << stringf(" %% "); - dump_attributes(f, "", cell->attributes, ' '); + dump_attributes(f, "", cell->attributes, " "); dump_cell_expr_port(f, cell, "B", true); f << stringf(";\n"); @@ -1330,7 +1330,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) f << stringf(" = "); dump_sigspec(f, cell->getPort(ID::S)); f << stringf(" ? "); - dump_attributes(f, "", cell->attributes, ' '); + dump_attributes(f, "", cell->attributes, " "); dump_sigspec(f, cell->getPort(ID::B)); f << stringf(" : "); dump_sigspec(f, cell->getPort(ID::A)); @@ -1439,7 +1439,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) f << stringf(" = "); dump_const(f, cell->parameters.at(ID::LUT)); f << stringf(" >> "); - dump_attributes(f, "", cell->attributes, ' '); + dump_attributes(f, "", cell->attributes, " "); dump_sigspec(f, cell->getPort(ID::A)); f << stringf(";\n"); return true; @@ -1971,6 +1971,56 @@ void dump_case_body(std::ostream &f, std::string indent, RTLIL::CaseRule *cs, bo f << stringf("%s" "end\n", indent.c_str()); } +bool dump_proc_switch_ifelse(std::ostream &f, std::string indent, RTLIL::SwitchRule *sw) +{ + for (auto it = sw->cases.begin(); it != sw->cases.end(); ++it) { + if ((*it)->compare.size() == 0) { + break; + } else if ((*it)->compare.size() == 1) { + int case_index = it - sw->cases.begin(); + SigSpec compare = (*it)->compare.at(0); + if (case_index >= compare.size()) + return false; + if (compare[case_index] != State::S1) + return false; + for (int bit_index = 0; bit_index < compare.size(); bit_index++) + if (bit_index != case_index && compare[bit_index] != State::Sa) + return false; + } else { + return false; + } + } + + f << indent; + auto sig_it = sw->signal.begin(); + for (auto it = sw->cases.begin(); it != sw->cases.end(); ++it, ++sig_it) { + bool had_newline = true; + if (it != sw->cases.begin()) { + if ((*it)->compare.empty()) { + f << indent << "else\n"; + had_newline = true; + } else { + f << indent << "else "; + had_newline = false; + } + } + if (!(*it)->compare.empty()) { + if (!(*it)->attributes.empty()) { + if (!had_newline) + f << "\n" << indent; + dump_attributes(f, "", (*it)->attributes, "\n" + indent); + } + f << stringf("if ("); + dump_sigspec(f, *sig_it); + f << stringf(")\n"); + } + dump_case_body(f, indent, *it); + if ((*it)->compare.empty()) + break; + } + return true; +} + void dump_proc_switch(std::ostream &f, std::string indent, RTLIL::SwitchRule *sw) { if (sw->signal.size() == 0) { @@ -1983,6 +2033,9 @@ void dump_proc_switch(std::ostream &f, std::string indent, RTLIL::SwitchRule *sw return; } + if (dump_proc_switch_ifelse(f, indent, sw)) + return; + dump_attributes(f, indent, sw->attributes); f << stringf("%s" "casez (", indent.c_str()); dump_sigspec(f, sw->signal); @@ -1990,7 +2043,7 @@ void dump_proc_switch(std::ostream &f, std::string indent, RTLIL::SwitchRule *sw for (auto it = sw->cases.begin(); it != sw->cases.end(); ++it) { bool got_default = false; - dump_attributes(f, indent + " ", (*it)->attributes, '\n', /*modattr=*/false, /*regattr=*/false, /*as_comment=*/true); + dump_attributes(f, indent + " ", (*it)->attributes, "\n", /*modattr=*/false, /*regattr=*/false, /*as_comment=*/true); if ((*it)->compare.size() == 0) { f << stringf("%s default", indent.c_str()); got_default = true; @@ -2173,7 +2226,7 @@ void dump_module(std::ostream &f, std::string indent, RTLIL::Module *module) } } - dump_attributes(f, indent, module->attributes, '\n', /*modattr=*/true); + dump_attributes(f, indent, module->attributes, "\n", /*modattr=*/true); f << stringf("%s" "module %s(", indent.c_str(), id(module->name, false).c_str()); bool keep_running = true; int cnt = 0; From e131a7895a8b0bd854f94be5d8440c66f737c6c6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 10 Jan 2024 00:16:19 +0000 Subject: [PATCH 29/70] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 946c323e7d0..c6d117fc524 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LDLIBS += -lrt endif endif -YOSYS_VER := 0.36+72 +YOSYS_VER := 0.36+79 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo From 2cab4ff17368156a73d9357f1ec5bd39d75ebdd6 Mon Sep 17 00:00:00 2001 From: Dag Lem Date: Fri, 4 Aug 2023 23:45:47 +0200 Subject: [PATCH 30/70] Correction and optimization of nowrshmsk This makes tests/verilog/dynamic_range_lhs.v pass, after ensuring that nowrshmsk is actually tested. Stride is extracted from indexing of two-dimensional packed arrays and variable slices on the form dst[i*stride +: width] = src, and is used to optimize the generated CASE block. Also uses less confusing variable names for indexing of lhs wires. --- frontends/ast/simplify.cc | 156 +++++++++++++++++++++--------- tests/verilog/dynamic_range_lhs.v | 2 +- 2 files changed, 113 insertions(+), 45 deletions(-) diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc index dfa1ed6af03..98a922ff472 100644 --- a/frontends/ast/simplify.cc +++ b/frontends/ast/simplify.cc @@ -35,12 +35,25 @@ #include #include #include +// For std::gcd in C++17 +// #include YOSYS_NAMESPACE_BEGIN using namespace AST; using namespace AST_INTERNAL; +// gcd computed by Euclidian division. +// To be replaced by C++17 std::gcd +template I gcd(I a, I b) { + while (b != 0) { + I tmp = b; + b = a%b; + a = tmp; + } + return std::abs(a); +} + void AstNode::set_in_lvalue_flag(bool flag, bool no_descend) { if (flag != in_lvalue_from_above) { @@ -2818,27 +2831,12 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin if (!children[0]->id2ast->range_valid) goto skip_dynamic_range_lvalue_expansion; - int source_width = children[0]->id2ast->range_left - children[0]->id2ast->range_right + 1; - int source_offset = children[0]->id2ast->range_right; - int result_width = 1; - int stride = 1; AST::AstNode *member_node = get_struct_member(children[0]); - if (member_node) { - // Clamp chunk to range of member within struct/union. - log_assert(!source_offset && !children[0]->id2ast->range_swapped); - source_width = member_node->range_left - member_node->range_right + 1; - - // When the (* nowrshmsk *) attribute is set, a CASE block is generated below - // to select the indexed bit slice. When a multirange array is indexed, the - // start of each possible slice is separated by the bit stride of the last - // index dimension, and we can optimize the CASE block accordingly. - // The dimension of the original array expression is saved in the 'integer' field. - int dims = children[0]->integer; - stride = source_width; - for (int dim = 0; dim < dims; dim++) { - stride /= get_struct_range_width(member_node, dim); - } - } + int wire_width = member_node ? + member_node->range_left - member_node->range_right + 1 : + children[0]->id2ast->range_left - children[0]->id2ast->range_right + 1; + int wire_offset = children[0]->id2ast->range_right; + int result_width = 1; AstNode *shift_expr = NULL; AstNode *range = children[0]->children[0]; @@ -2851,30 +2849,102 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin else shift_expr = range->children[0]->clone(); - bool use_case_method = false; - - if (children[0]->id2ast->attributes.count(ID::nowrshmsk)) { - AstNode *node = children[0]->id2ast->attributes.at(ID::nowrshmsk); - while (node->simplify(true, stage, -1, false)) { } - if (node->type != AST_CONSTANT) - input_error("Non-constant value for `nowrshmsk' attribute on `%s'!\n", children[0]->id2ast->str.c_str()); - if (node->asAttrConst().as_bool()) - use_case_method = true; - } + bool use_case_method = children[0]->id2ast->get_bool_attribute(ID::nowrshmsk); if (!use_case_method && current_always->detect_latch(children[0]->str)) use_case_method = true; - if (use_case_method) - { + if (use_case_method) { // big case block + int stride = 1; + long long bitno_div = stride; + + int case_width_hint; + bool case_sign_hint; + shift_expr->detectSignWidth(case_width_hint, case_sign_hint); + int max_width = case_width_hint; + + if (member_node) { // Member in packed struct/union + // Clamp chunk to range of member within struct/union. + log_assert(!wire_offset && !children[0]->id2ast->range_swapped); + + // When the (* nowrshmsk *) attribute is set, a CASE block is generated below + // to select the indexed bit slice. When a multirange array is indexed, the + // start of each possible slice is separated by the bit stride of the last + // index dimension, and we can optimize the CASE block accordingly. + // The dimension of the original array expression is saved in the 'integer' field. + int dims = children[0]->integer; + stride = wire_width; + for (int dim = 0; dim < dims; dim++) { + stride /= get_struct_range_width(member_node, dim); + } + bitno_div = stride; + } else { + // Extract (index)*(width) from non_opt_range pattern ((@selfsz@((index)*(width)))+(0)). + AstNode *lsb_expr = + shift_expr->type == AST_ADD && shift_expr->children[0]->type == AST_SELFSZ && + shift_expr->children[1]->type == AST_CONSTANT && shift_expr->children[1]->integer == 0 ? + shift_expr->children[0]->children[0] : + shift_expr; + + // Extract stride from indexing of two-dimensional packed arrays and + // variable slices on the form dst[i*stride +: width] = src. + if (lsb_expr->type == AST_MUL && + (lsb_expr->children[0]->type == AST_CONSTANT || + lsb_expr->children[1]->type == AST_CONSTANT)) + { + int stride_ix = lsb_expr->children[1]->type == AST_CONSTANT; + stride = (int)lsb_expr->children[stride_ix]->integer; + bitno_div = stride != 0 ? stride : 1; + + // Check whether i*stride can overflow. + int i_width; + bool i_sign; + lsb_expr->children[1 - stride_ix]->detectSignWidth(i_width, i_sign); + int stride_width; + bool stride_sign; + lsb_expr->children[stride_ix]->detectSignWidth(stride_width, stride_sign); + max_width = std::max(i_width, stride_width); + // Stride width calculated from actual stride value. + stride_width = std::ceil(std::log2(std::abs(stride))); + + if (i_width + stride_width > max_width) { + // For (truncated) i*stride to be within the range of dst, the following must hold: + // i*stride ≡ bitno (mod shift_mod), i.e. + // i*stride = k*shift_mod + bitno + // + // The Diophantine equation on the form ax + by = c: + // stride*i - shift_mod*k = bitno + // has solutions iff c is a multiple of d = gcd(a, b), i.e. + // bitno mod gcd(stride, shift_mod) = 0 + // + // long long is at least 64 bits in C++11 + long long shift_mod = 1ll << (max_width - case_sign_hint); + // std::gcd requires C++17 + // bitno_div = std::gcd(stride, shift_mod); + bitno_div = gcd((long long)stride, shift_mod); + } + } + } + + // long long is at least 64 bits in C++11 + long long max_offset = (1ll << (max_width - case_sign_hint)) - 1; + long long min_offset = case_sign_hint ? -(1ll << (max_width - 1)) : 0; + did_something = true; newNode = new AstNode(AST_CASE, shift_expr); - for (int i = 0; i < source_width; i += stride) { - int start_bit = source_offset + i; - int end_bit = std::min(start_bit+result_width,source_width) - 1; - AstNode *cond = new AstNode(AST_COND, mkconst_int(start_bit, true)); + for (int i = 1 - result_width; i < wire_width; i++) { + // Out of range indexes are handled in genrtlil.cc + int start_bit = wire_offset + i; + int end_bit = start_bit + result_width - 1; + // Check whether the current index can be generated by shift_expr. + if (start_bit < min_offset || start_bit > max_offset) + continue; + if (start_bit%bitno_div != 0 || (stride == 0 && start_bit != 0)) + continue; + + AstNode *cond = new AstNode(AST_COND, mkconst_int(start_bit, case_sign_hint, max_width)); AstNode *lvalue = children[0]->clone(); lvalue->delete_children(); if (member_node) @@ -2884,19 +2954,17 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin cond->children.push_back(new AstNode(AST_BLOCK, new AstNode(type, lvalue, children[1]->clone()))); newNode->children.push_back(cond); } - } - else - { - // mask and shift operations, disabled for now + } else { + // mask and shift operations - AstNode *wire_mask = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(source_width-1, true), mkconst_int(0, true))); + AstNode *wire_mask = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(wire_width-1, true), mkconst_int(0, true))); wire_mask->str = stringf("$bitselwrite$mask$%s:%d$%d", RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++); wire_mask->set_attribute(ID::nosync, AstNode::mkconst_int(1, false)); wire_mask->is_logic = true; while (wire_mask->simplify(true, 1, -1, false)) { } current_ast_mod->children.push_back(wire_mask); - AstNode *wire_data = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(source_width-1, true), mkconst_int(0, true))); + AstNode *wire_data = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(wire_width-1, true), mkconst_int(0, true))); wire_data->str = stringf("$bitselwrite$data$%s:%d$%d", RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++); wire_data->set_attribute(ID::nosync, AstNode::mkconst_int(1, false)); wire_data->is_logic = true; @@ -2952,12 +3020,12 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin shamt = new AstNode(AST_TO_SIGNED, shamt); // offset the shift amount by the lower bound of the dimension - int start_bit = source_offset; + int start_bit = wire_offset; shamt = new AstNode(AST_SUB, shamt, mkconst_int(start_bit, true)); // reflect the shift amount if the dimension is swapped if (children[0]->id2ast->range_swapped) - shamt = new AstNode(AST_SUB, mkconst_int(source_width - result_width, true), shamt); + shamt = new AstNode(AST_SUB, mkconst_int(wire_width - result_width, true), shamt); // AST_SHIFT uses negative amounts for shifting left shamt = new AstNode(AST_NEG, shamt); diff --git a/tests/verilog/dynamic_range_lhs.v b/tests/verilog/dynamic_range_lhs.v index ae291374db9..56fe3ef3b32 100644 --- a/tests/verilog/dynamic_range_lhs.v +++ b/tests/verilog/dynamic_range_lhs.v @@ -1,6 +1,6 @@ module gate( - output reg [`LEFT:`RIGHT] out_u, out_s, (* nowrshmsk = `ALT *) + output reg [`LEFT:`RIGHT] out_u, out_s, input wire data, input wire [1:0] sel1, sel2 ); From a105d2c050eda59a2bbc7af14190ed2b050dc061 Mon Sep 17 00:00:00 2001 From: Dag Lem Date: Wed, 22 Nov 2023 06:52:04 +0100 Subject: [PATCH 31/70] Add torture test for (* nowrshmsk *) stride optimization --- tests/various/dynamic_part_select.ys | 18 +++++++++++++++++ .../forloop_select_nowrshmsk.v | 20 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 tests/various/dynamic_part_select/forloop_select_nowrshmsk.v diff --git a/tests/various/dynamic_part_select.ys b/tests/various/dynamic_part_select.ys index 2dc061e899b..9e303b9db2e 100644 --- a/tests/various/dynamic_part_select.ys +++ b/tests/various/dynamic_part_select.ys @@ -69,6 +69,24 @@ design -copy-from gate -as gate gate miter -equiv -make_assert -make_outcmp -flatten gold gate equiv sat -prove-asserts -seq 10 -show-public -verify -set-init-zero equiv +### For-loop select, one dynamic input, (* nowrshmsk *) +design -reset +read_verilog ./dynamic_part_select/forloop_select_nowrshmsk.v +proc +rename -top gold +design -stash gold + +read_verilog ./dynamic_part_select/forloop_select_gate.v +proc +rename -top gate +design -stash gate + +design -copy-from gold -as gold gold +design -copy-from gate -as gate gate + +miter -equiv -make_assert -make_outcmp -flatten gold gate equiv +sat -prove-asserts -seq 10 -show-public -verify -set-init-zero equiv + #### Double loop (part-select, reset) ### design -reset read_verilog ./dynamic_part_select/reset_test.v diff --git a/tests/various/dynamic_part_select/forloop_select_nowrshmsk.v b/tests/various/dynamic_part_select/forloop_select_nowrshmsk.v new file mode 100644 index 00000000000..75415c3130e --- /dev/null +++ b/tests/various/dynamic_part_select/forloop_select_nowrshmsk.v @@ -0,0 +1,20 @@ +`default_nettype none +module forloop_select #(parameter WIDTH=16, SELW=4, CTRLW=$clog2(WIDTH), DINW=2**SELW) + (input wire clk, + input wire [CTRLW-1:0] ctrl, + input wire [DINW-1:0] din, + input wire en, + (* nowrshmsk *) + output reg [WIDTH-1:0] dout); + + reg [SELW:0] sel; + localparam SLICE = WIDTH/(SELW**2); + + always @(posedge clk) + begin + if (en) begin + for (sel = 0; sel <= 4'hf; sel=sel+1'b1) + dout[(ctrl*sel)+:SLICE] <= din; + end + end +endmodule From dbec704b49aebd8f1ac5ff9ed36b357cd9b99973 Mon Sep 17 00:00:00 2001 From: Dag Lem Date: Thu, 7 Dec 2023 13:45:56 +0100 Subject: [PATCH 32/70] Include x bits in test of lhs dynamic part-select --- tests/verilog/dynamic_range_lhs.sh | 2 +- tests/verilog/dynamic_range_lhs.v | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/verilog/dynamic_range_lhs.sh b/tests/verilog/dynamic_range_lhs.sh index 77b4a291834..f36c74bd2c8 100755 --- a/tests/verilog/dynamic_range_lhs.sh +++ b/tests/verilog/dynamic_range_lhs.sh @@ -15,7 +15,7 @@ run() { -p "read_verilog dynamic_range_lhs.v" \ -p "proc" \ -p "equiv_make gold gate equiv" \ - -p "equiv_simple" \ + -p "equiv_simple -undef" \ -p "equiv_status -assert" } diff --git a/tests/verilog/dynamic_range_lhs.v b/tests/verilog/dynamic_range_lhs.v index 56fe3ef3b32..6eb95216571 100644 --- a/tests/verilog/dynamic_range_lhs.v +++ b/tests/verilog/dynamic_range_lhs.v @@ -5,8 +5,8 @@ module gate( input wire [1:0] sel1, sel2 ); always @* begin - out_u = 0; - out_s = 0; + out_u = 'x; + out_s = 'x; case (`SPAN) 1: begin out_u[sel1*sel2] = data; @@ -43,8 +43,8 @@ task set; out_s[b] = data; endtask always @* begin - out_u = 0; - out_s = 0; + out_u = 'x; + out_s = 'x; case (sel1*sel2) 2'b00: set(0, 0); 2'b01: set(1, 1); From 1a2b4759e85928d305f9b082d30f0b8a2fc46e0e Mon Sep 17 00:00:00 2001 From: Dag Lem Date: Fri, 8 Dec 2023 20:47:43 +0100 Subject: [PATCH 33/70] Assign from rvalue via temporary register in nowrshmsk CASE Avoid repeating complex rvalue expressions for each condition. --- frontends/ast/ast.cc | 19 +++++++++++++++++++ frontends/ast/ast.h | 3 +++ frontends/ast/simplify.cc | 16 +++++++++++++--- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/frontends/ast/ast.cc b/frontends/ast/ast.cc index 34e6249939e..4b2b7a8220d 100644 --- a/frontends/ast/ast.cc +++ b/frontends/ast/ast.cc @@ -850,6 +850,25 @@ AstNode *AstNode::mkconst_str(const std::string &str) return node; } +// create a temporary register +AstNode *AstNode::mktemp_logic(const std::string &name, AstNode *mod, bool nosync, int range_left, int range_right, bool is_signed) +{ + AstNode *wire = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(range_left, true), mkconst_int(range_right, true))); + wire->str = stringf("%s%s:%d$%d", name.c_str(), RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++); + if (nosync) + wire->set_attribute(ID::nosync, AstNode::mkconst_int(1, false)); + wire->is_signed = is_signed; + wire->is_logic = true; + mod->children.push_back(wire); + while (wire->simplify(true, 1, -1, false)) { } + + AstNode *ident = new AstNode(AST_IDENTIFIER); + ident->str = wire->str; + ident->id2ast = wire; + + return ident; +} + bool AstNode::bits_only_01() const { for (auto bit : bits) diff --git a/frontends/ast/ast.h b/frontends/ast/ast.h index f789e930b3e..97903d0a046 100644 --- a/frontends/ast/ast.h +++ b/frontends/ast/ast.h @@ -321,6 +321,9 @@ namespace AST static AstNode *mkconst_str(const std::vector &v); static AstNode *mkconst_str(const std::string &str); + // helper function to create an AST node for a temporary register + AstNode *mktemp_logic(const std::string &name, AstNode *mod, bool nosync, int range_left, int range_right, bool is_signed); + // helper function for creating sign-extended const objects RTLIL::Const bitsAsConst(int width, bool is_signed); RTLIL::Const bitsAsConst(int width = -1); diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc index 98a922ff472..945f286a1f6 100644 --- a/frontends/ast/simplify.cc +++ b/frontends/ast/simplify.cc @@ -2932,8 +2932,18 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin long long max_offset = (1ll << (max_width - case_sign_hint)) - 1; long long min_offset = case_sign_hint ? -(1ll << (max_width - 1)) : 0; + // A temporary register holds the result of the (possibly complex) rvalue expression, + // avoiding repetition in each AST_COND below. + int rvalue_width; + bool rvalue_sign; + children[1]->detectSignWidth(rvalue_width, rvalue_sign); + AstNode *rvalue = mktemp_logic("$bitselwrite$rvalue$", current_ast_mod, true, rvalue_width - 1, 0, rvalue_sign); + AstNode *caseNode = new AstNode(AST_CASE, shift_expr); + newNode = new AstNode(AST_BLOCK, + new AstNode(AST_ASSIGN_EQ, rvalue, children[1]->clone()), + caseNode); + did_something = true; - newNode = new AstNode(AST_CASE, shift_expr); for (int i = 1 - result_width; i < wire_width; i++) { // Out of range indexes are handled in genrtlil.cc int start_bit = wire_offset + i; @@ -2951,8 +2961,8 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin lvalue->set_attribute(ID::wiretype, member_node->clone()); lvalue->children.push_back(new AstNode(AST_RANGE, mkconst_int(end_bit, true), mkconst_int(start_bit, true))); - cond->children.push_back(new AstNode(AST_BLOCK, new AstNode(type, lvalue, children[1]->clone()))); - newNode->children.push_back(cond); + cond->children.push_back(new AstNode(AST_BLOCK, new AstNode(type, lvalue, rvalue->clone()))); + caseNode->children.push_back(cond); } } else { // mask and shift operations From 23cd23efc56147c5f2a8645dccd65ae7c189b89d Mon Sep 17 00:00:00 2001 From: Dag Lem Date: Tue, 12 Dec 2023 13:37:34 +0100 Subject: [PATCH 34/70] Simplify and correct AST for array slice assignment Corrects sign extension of the right hand side, and hopefully makes the code simpler to understand. Fixes #4064 --- frontends/ast/simplify.cc | 111 ++++++++++++++------------------------ 1 file changed, 41 insertions(+), 70 deletions(-) diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc index 945f286a1f6..51ed9562163 100644 --- a/frontends/ast/simplify.cc +++ b/frontends/ast/simplify.cc @@ -2966,96 +2966,67 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin } } else { // mask and shift operations - - AstNode *wire_mask = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(wire_width-1, true), mkconst_int(0, true))); - wire_mask->str = stringf("$bitselwrite$mask$%s:%d$%d", RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++); - wire_mask->set_attribute(ID::nosync, AstNode::mkconst_int(1, false)); - wire_mask->is_logic = true; - while (wire_mask->simplify(true, 1, -1, false)) { } - current_ast_mod->children.push_back(wire_mask); - - AstNode *wire_data = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(wire_width-1, true), mkconst_int(0, true))); - wire_data->str = stringf("$bitselwrite$data$%s:%d$%d", RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++); - wire_data->set_attribute(ID::nosync, AstNode::mkconst_int(1, false)); - wire_data->is_logic = true; - while (wire_data->simplify(true, 1, -1, false)) { } - current_ast_mod->children.push_back(wire_data); - - int shamt_width_hint = -1; - bool shamt_sign_hint = true; - shift_expr->detectSignWidth(shamt_width_hint, shamt_sign_hint); - - AstNode *wire_sel = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(shamt_width_hint-1, true), mkconst_int(0, true))); - wire_sel->str = stringf("$bitselwrite$sel$%s:%d$%d", RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++); - wire_sel->set_attribute(ID::nosync, AstNode::mkconst_int(1, false)); - wire_sel->is_logic = true; - wire_sel->is_signed = shamt_sign_hint; - while (wire_sel->simplify(true, 1, -1, false)) { } - current_ast_mod->children.push_back(wire_sel); - - did_something = true; - newNode = new AstNode(AST_BLOCK); + // dst = (dst & ~(width'1 << lsb)) | unsigned'(width'(src)) << lsb) AstNode *lvalue = children[0]->clone(); lvalue->delete_children(); if (member_node) lvalue->set_attribute(ID::wiretype, member_node->clone()); - AstNode *ref_mask = new AstNode(AST_IDENTIFIER); - ref_mask->str = wire_mask->str; - ref_mask->id2ast = wire_mask; - ref_mask->was_checked = true; - - AstNode *ref_data = new AstNode(AST_IDENTIFIER); - ref_data->str = wire_data->str; - ref_data->id2ast = wire_data; - ref_data->was_checked = true; - - AstNode *ref_sel = new AstNode(AST_IDENTIFIER); - ref_sel->str = wire_sel->str; - ref_sel->id2ast = wire_sel; - ref_sel->was_checked = true; - AstNode *old_data = lvalue->clone(); if (type == AST_ASSIGN_LE) old_data->lookahead = true; - AstNode *s = new AstNode(AST_ASSIGN_EQ, ref_sel->clone(), shift_expr); - newNode->children.push_back(s); + int shift_width_hint; + bool shift_sign_hint; + shift_expr->detectSignWidth(shift_width_hint, shift_sign_hint); + + // All operations are carried out in a new block. + newNode = new AstNode(AST_BLOCK); + + // Temporary register holding the result of the bit- or part-select position expression. + AstNode *pos = mktemp_logic("$bitselwrite$pos$", current_ast_mod, true, shift_width_hint - 1, 0, shift_sign_hint); + newNode->children.push_back(new AstNode(AST_ASSIGN_EQ, pos, shift_expr)); - AstNode *shamt = ref_sel; + // Calculate lsb from position. + AstNode *shift_val = pos->clone(); - // convert to signed while preserving the sign and value - shamt = new AstNode(AST_CAST_SIZE, mkconst_int(shamt_width_hint + 1, true), shamt); - shamt = new AstNode(AST_TO_SIGNED, shamt); + // If the expression is signed, we must add an extra bit for possible negation of the most negative number. + // If the expression is unsigned, we must add an extra bit for sign. + shift_val = new AstNode(AST_CAST_SIZE, mkconst_int(shift_width_hint + 1, true), shift_val); + if (!shift_sign_hint) + shift_val = new AstNode(AST_TO_SIGNED, shift_val); // offset the shift amount by the lower bound of the dimension - int start_bit = wire_offset; - shamt = new AstNode(AST_SUB, shamt, mkconst_int(start_bit, true)); + if (wire_offset != 0) + shift_val = new AstNode(AST_SUB, shift_val, mkconst_int(wire_offset, true)); // reflect the shift amount if the dimension is swapped if (children[0]->id2ast->range_swapped) - shamt = new AstNode(AST_SUB, mkconst_int(wire_width - result_width, true), shamt); + shift_val = new AstNode(AST_SUB, mkconst_int(wire_width - result_width, true), shift_val); // AST_SHIFT uses negative amounts for shifting left - shamt = new AstNode(AST_NEG, shamt); + shift_val = new AstNode(AST_NEG, shift_val); - AstNode *t; - - t = mkconst_bits(std::vector(result_width, State::S1), false); - t = new AstNode(AST_SHIFT, t, shamt->clone()); - t = new AstNode(AST_ASSIGN_EQ, ref_mask->clone(), t); - newNode->children.push_back(t); - - t = new AstNode(AST_BIT_AND, mkconst_bits(std::vector(result_width, State::S1), false), children[1]->clone()); - t = new AstNode(AST_SHIFT, t, shamt); - t = new AstNode(AST_ASSIGN_EQ, ref_data->clone(), t); - newNode->children.push_back(t); - - t = new AstNode(AST_BIT_AND, old_data, new AstNode(AST_BIT_NOT, ref_mask)); - t = new AstNode(AST_BIT_OR, t, ref_data); - t = new AstNode(type, lvalue, t); - newNode->children.push_back(t); + // dst = (dst & ~(width'1 << lsb)) | unsigned'(width'(src)) << lsb) + did_something = true; + AstNode *bitmask = mkconst_bits(std::vector(result_width, State::S1), false); + newNode->children.push_back( + new AstNode(type, + lvalue, + new AstNode(AST_BIT_OR, + new AstNode(AST_BIT_AND, + old_data, + new AstNode(AST_BIT_NOT, + new AstNode(AST_SHIFT, + bitmask, + shift_val->clone()))), + new AstNode(AST_SHIFT, + new AstNode(AST_TO_UNSIGNED, + new AstNode(AST_CAST_SIZE, + mkconst_int(result_width, true), + children[1]->clone())), + shift_val)))); newNode->fixup_hierarchy_flags(true); } From e0566eafdbdbe0b99b944edb73fe351149dc380a Mon Sep 17 00:00:00 2001 From: Dag Lem Date: Tue, 12 Dec 2023 14:16:45 +0100 Subject: [PATCH 35/70] Add test for rhs sign extension in array slice assignment --- tests/simple/sign_part_assign.v | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/simple/sign_part_assign.v diff --git a/tests/simple/sign_part_assign.v b/tests/simple/sign_part_assign.v new file mode 100644 index 00000000000..5f65ac28402 --- /dev/null +++ b/tests/simple/sign_part_assign.v @@ -0,0 +1,20 @@ +module test ( + offset_i, + data_o, + data_ref_o +); +input wire [ 2:0] offset_i; +output reg [15:0] data_o; +output reg [15:0] data_ref_o; + +always @(*) begin + // defaults + data_o = '0; + data_ref_o = '0; + + // partial assigns + data_ref_o[offset_i+:4] = 4'b1111; // unsigned + data_o[offset_i+:4] = 1'sb1; // sign extension to 4'b1111 +end + +endmodule From f26495e54d936830e067e66b91bfac824011897c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 11 Jan 2024 00:16:28 +0000 Subject: [PATCH 36/70] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c6d117fc524..8bde98a1ce2 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LDLIBS += -lrt endif endif -YOSYS_VER := 0.36+79 +YOSYS_VER := 0.36+85 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo From 57b4e16acdf715092e948dc791d017aeb8130e17 Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Fri, 22 Sep 2023 17:52:25 +0200 Subject: [PATCH 37/70] sim: Include $display output in JSON summary This allows tools like SBY to capture the $display output independent from anything else sim might log. Additionally it provides source and hierarchy locations for everything printed. --- passes/sat/sim.cc | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/passes/sat/sim.cc b/passes/sat/sim.cc index 8c4fadb699b..b0753418468 100644 --- a/passes/sat/sim.cc +++ b/passes/sat/sim.cc @@ -88,6 +88,17 @@ struct TriggeredAssertion { { } }; +struct DisplayOutput { + int step; + SimInstance *instance; + Cell *cell; + std::string output; + + DisplayOutput(int step, SimInstance *instance, Cell *cell, std::string output) : + step(step), instance(instance), cell(cell), output(output) + { } +}; + struct SimShared { bool debug = false; @@ -110,6 +121,7 @@ struct SimShared int next_output_id = 0; int step = 0; std::vector triggered_assertions; + std::vector display_output; bool serious_asserts = false; bool initstate = true; }; @@ -870,6 +882,7 @@ struct SimInstance std::string rendered = print.fmt.render(); log("%s", rendered.c_str()); + shared->display_output.emplace_back(shared->step, this, cell, rendered); } update_print: @@ -2055,6 +2068,20 @@ struct SimWorker : SimShared json.end_object(); } json.end_array(); + json.name("display_output"); + json.begin_array(); + for (auto &output : display_output) { + json.begin_object(); + json.entry("step", output.step); + json.entry("path", output.instance->witness_full_path(output.cell)); + auto src = output.cell->get_string_attribute(ID::src); + if (!src.empty()) { + json.entry("src", src); + } + json.entry("output", output.output); + json.end_object(); + } + json.end_array(); json.end_object(); } From 510d137996c3beb7588aa86687315908928a9778 Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Fri, 22 Sep 2023 17:56:34 +0200 Subject: [PATCH 38/70] fmt: Allow non-constant $display calls in initial blocks These are useful for formal verification with SBY where they can be used to display solver chosen `rand const reg` signals and signals derived from those. The previous error message for non-constant initial $display statements is downgraded to a log message. Constant initial $display statements will be shown both during elaboration and become part of the RTLIL so that the `sim` output is complete. --- frontends/ast/ast.h | 2 +- frontends/ast/simplify.cc | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/frontends/ast/ast.h b/frontends/ast/ast.h index 97903d0a046..31679e4dff0 100644 --- a/frontends/ast/ast.h +++ b/frontends/ast/ast.h @@ -287,7 +287,7 @@ namespace AST bool is_simple_const_expr(); // helper for parsing format strings - Fmt processFormat(int stage, bool sformat_like, int default_base = 10, size_t first_arg_at = 0); + Fmt processFormat(int stage, bool sformat_like, int default_base = 10, size_t first_arg_at = 0, bool may_fail = false); bool is_recursive_function() const; std::pair get_tern_choice(); diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc index 51ed9562163..d45b53db56c 100644 --- a/frontends/ast/simplify.cc +++ b/frontends/ast/simplify.cc @@ -145,7 +145,7 @@ void AstNode::fixup_hierarchy_flags(bool force_descend) // Process a format string and arguments for $display, $write, $sprintf, etc -Fmt AstNode::processFormat(int stage, bool sformat_like, int default_base, size_t first_arg_at) { +Fmt AstNode::processFormat(int stage, bool sformat_like, int default_base, size_t first_arg_at, bool may_fail) { std::vector args; for (size_t index = first_arg_at; index < children.size(); index++) { AstNode *node_arg = children[index]; @@ -169,6 +169,9 @@ Fmt AstNode::processFormat(int stage, bool sformat_like, int default_base, size_ arg.type = VerilogFmtArg::INTEGER; arg.sig = node_arg->bitsAsConst(); arg.signed_ = node_arg->is_signed; + } else if (may_fail) { + log_file_info(filename, location.first_line, "Skipping system task `%s' with non-constant argument at position %zu.\n", str.c_str(), index + 1); + return Fmt(); } else { log_file_error(filename, location.first_line, "Failed to evaluate system task `%s' with non-constant argument at position %zu.\n", str.c_str(), index + 1); } @@ -1065,10 +1068,13 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin default_base = 16; // when $display()/$write() functions are used in an initial block, print them during synthesis - Fmt fmt = processFormat(stage, /*sformat_like=*/false, default_base); + Fmt fmt = processFormat(stage, /*sformat_like=*/false, default_base, /*first_arg_at=*/0, /*may_fail=*/true); if (str.substr(0, 8) == "$display") fmt.append_string("\n"); log("%s", fmt.render().c_str()); + for (auto node : children) + while (node->simplify(true, stage, -1, false)) {} + return false; } else { // when $display()/$write() functions are used in an always block, simplify the expressions and // convert them to a special cell later in genrtlil From 3ed9030eb4263710f0a808f57b0b8c896d7792ab Mon Sep 17 00:00:00 2001 From: Dag Lem Date: Thu, 11 Jan 2024 10:32:44 +0100 Subject: [PATCH 39/70] Optionally suppress output from display system tasks in read_verilog --- frontends/ast/ast.cc | 5 ++-- frontends/ast/ast.h | 4 +-- frontends/ast/simplify.cc | 42 +++++++++++++-------------- frontends/verilog/verilog_frontend.cc | 11 ++++++- 4 files changed, 35 insertions(+), 27 deletions(-) diff --git a/frontends/ast/ast.cc b/frontends/ast/ast.cc index 4b2b7a8220d..fe075b27062 100644 --- a/frontends/ast/ast.cc +++ b/frontends/ast/ast.cc @@ -45,7 +45,7 @@ namespace AST { // instantiate global variables (private API) namespace AST_INTERNAL { - bool flag_dump_ast1, flag_dump_ast2, flag_no_dump_ptr, flag_dump_vlog1, flag_dump_vlog2, flag_dump_rtlil, flag_nolatches, flag_nomeminit; + bool flag_nodisplay, flag_dump_ast1, flag_dump_ast2, flag_no_dump_ptr, flag_dump_vlog1, flag_dump_vlog2, flag_dump_rtlil, flag_nolatches, flag_nomeminit; bool flag_nomem2reg, flag_mem2reg, flag_noblackbox, flag_lib, flag_nowb, flag_noopt, flag_icells, flag_pwires, flag_autowire; AstNode *current_ast, *current_ast_mod; std::map current_scope; @@ -1320,11 +1320,12 @@ static void rename_in_package_stmts(AstNode *pkg) } // create AstModule instances for all modules in the AST tree and add them to 'design' -void AST::process(RTLIL::Design *design, AstNode *ast, bool dump_ast1, bool dump_ast2, bool no_dump_ptr, bool dump_vlog1, bool dump_vlog2, bool dump_rtlil, +void AST::process(RTLIL::Design *design, AstNode *ast, bool nodisplay, bool dump_ast1, bool dump_ast2, bool no_dump_ptr, bool dump_vlog1, bool dump_vlog2, bool dump_rtlil, bool nolatches, bool nomeminit, bool nomem2reg, bool mem2reg, bool noblackbox, bool lib, bool nowb, bool noopt, bool icells, bool pwires, bool nooverwrite, bool overwrite, bool defer, bool autowire) { current_ast = ast; current_ast_mod = nullptr; + flag_nodisplay = nodisplay; flag_dump_ast1 = dump_ast1; flag_dump_ast2 = dump_ast2; flag_no_dump_ptr = no_dump_ptr; diff --git a/frontends/ast/ast.h b/frontends/ast/ast.h index 31679e4dff0..c447461312a 100644 --- a/frontends/ast/ast.h +++ b/frontends/ast/ast.h @@ -376,7 +376,7 @@ namespace AST }; // process an AST tree (ast must point to an AST_DESIGN node) and generate RTLIL code - void process(RTLIL::Design *design, AstNode *ast, bool dump_ast1, bool dump_ast2, bool no_dump_ptr, bool dump_vlog1, bool dump_vlog2, bool dump_rtlil, bool nolatches, bool nomeminit, + void process(RTLIL::Design *design, AstNode *ast, bool nodisplay, bool dump_ast1, bool dump_ast2, bool no_dump_ptr, bool dump_vlog1, bool dump_vlog2, bool dump_rtlil, bool nolatches, bool nomeminit, bool nomem2reg, bool mem2reg, bool noblackbox, bool lib, bool nowb, bool noopt, bool icells, bool pwires, bool nooverwrite, bool overwrite, bool defer, bool autowire); // parametric modules are supported directly by the AST library @@ -432,7 +432,7 @@ namespace AST namespace AST_INTERNAL { // internal state variables - extern bool flag_dump_ast1, flag_dump_ast2, flag_no_dump_ptr, flag_dump_rtlil, flag_nolatches, flag_nomeminit; + extern bool flag_nodisplay, flag_dump_ast1, flag_dump_ast2, flag_no_dump_ptr, flag_dump_rtlil, flag_nolatches, flag_nomeminit; extern bool flag_nomem2reg, flag_mem2reg, flag_lib, flag_noopt, flag_icells, flag_pwires, flag_autowire; extern AST::AstNode *current_ast, *current_ast_mod; extern std::map current_scope; diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc index d45b53db56c..5370c4187c2 100644 --- a/frontends/ast/simplify.cc +++ b/frontends/ast/simplify.cc @@ -1058,33 +1058,31 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin { if (!current_always) { log_file_warning(filename, location.first_line, "System task `%s' outside initial or always block is unsupported.\n", str.c_str()); - } else if (current_always->type == AST_INITIAL) { - int default_base = 10; - if (str.back() == 'b') - default_base = 2; - else if (str.back() == 'o') - default_base = 8; - else if (str.back() == 'h') - default_base = 16; - - // when $display()/$write() functions are used in an initial block, print them during synthesis - Fmt fmt = processFormat(stage, /*sformat_like=*/false, default_base, /*first_arg_at=*/0, /*may_fail=*/true); - if (str.substr(0, 8) == "$display") - fmt.append_string("\n"); - log("%s", fmt.render().c_str()); - for (auto node : children) - while (node->simplify(true, stage, -1, false)) {} - return false; + delete_children(); + str = std::string(); } else { - // when $display()/$write() functions are used in an always block, simplify the expressions and - // convert them to a special cell later in genrtlil + // simplify the expressions and convert them to a special cell later in genrtlil for (auto node : children) while (node->simplify(true, stage, -1, false)) {} + + if (current_always->type == AST_INITIAL && !flag_nodisplay && stage == 2) { + int default_base = 10; + if (str.back() == 'b') + default_base = 2; + else if (str.back() == 'o') + default_base = 8; + else if (str.back() == 'h') + default_base = 16; + + // when $display()/$write() functions are used in an initial block, print them during synthesis + Fmt fmt = processFormat(stage, /*sformat_like=*/false, default_base, /*first_arg_at=*/0, /*may_fail=*/true); + if (str.substr(0, 8) == "$display") + fmt.append_string("\n"); + log("%s", fmt.render().c_str()); + } + return false; } - - delete_children(); - str = std::string(); } // activate const folding if this is anything that must be evaluated statically (ranges, parameters, attributes, etc.) diff --git a/frontends/verilog/verilog_frontend.cc b/frontends/verilog/verilog_frontend.cc index 9b277c6b977..5c59fe3afac 100644 --- a/frontends/verilog/verilog_frontend.cc +++ b/frontends/verilog/verilog_frontend.cc @@ -100,6 +100,10 @@ struct VerilogFrontend : public Frontend { log(" -assert-assumes\n"); log(" treat all assume() statements like assert() statements\n"); log("\n"); + log(" -nodisplay\n"); + log(" suppress output from display system tasks ($display et. al).\n"); + log(" This does not affect the output from a later 'sim' command.\n"); + log("\n"); log(" -debug\n"); log(" alias for -dump_ast1 -dump_ast2 -dump_vlog1 -dump_vlog2 -yydebug\n"); log("\n"); @@ -235,6 +239,7 @@ struct VerilogFrontend : public Frontend { } void execute(std::istream *&f, std::string filename, std::vector args, RTLIL::Design *design) override { + bool flag_nodisplay = false; bool flag_dump_ast1 = false; bool flag_dump_ast2 = false; bool flag_no_dump_ptr = false; @@ -308,6 +313,10 @@ struct VerilogFrontend : public Frontend { assert_assumes_mode = true; continue; } + if (arg == "-nodisplay") { + flag_nodisplay = true; + continue; + } if (arg == "-debug") { flag_dump_ast1 = true; flag_dump_ast2 = true; @@ -510,7 +519,7 @@ struct VerilogFrontend : public Frontend { if (flag_nodpi) error_on_dpi_function(current_ast); - AST::process(design, current_ast, flag_dump_ast1, flag_dump_ast2, flag_no_dump_ptr, flag_dump_vlog1, flag_dump_vlog2, flag_dump_rtlil, flag_nolatches, + AST::process(design, current_ast, flag_nodisplay, flag_dump_ast1, flag_dump_ast2, flag_no_dump_ptr, flag_dump_vlog1, flag_dump_vlog2, flag_dump_rtlil, flag_nolatches, flag_nomeminit, flag_nomem2reg, flag_mem2reg, flag_noblackbox, lib_mode, flag_nowb, flag_noopt, flag_icells, flag_pwires, flag_nooverwrite, flag_overwrite, flag_defer, default_nettype_wire); From 0486f61a35331e3cf557424940da6f20253e2d92 Mon Sep 17 00:00:00 2001 From: Catherine Date: Thu, 11 Jan 2024 10:53:30 +0000 Subject: [PATCH 40/70] write_verilog: emit zero width parameters as `.PARAM()`. --- backends/verilog/verilog_backend.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backends/verilog/verilog_backend.cc b/backends/verilog/verilog_backend.cc index 9ff2c5c8606..71b9c5fd80d 100644 --- a/backends/verilog/verilog_backend.cc +++ b/backends/verilog/verilog_backend.cc @@ -1830,7 +1830,8 @@ void dump_cell(std::ostream &f, std::string indent, RTLIL::Cell *cell) if (it != cell->parameters.begin()) f << stringf(","); f << stringf("\n%s .%s(", indent.c_str(), id(it->first).c_str()); - dump_const(f, it->second); + if (it->second.size() > 0) + dump_const(f, it->second); f << stringf(")"); } f << stringf("\n%s" ")", indent.c_str()); From 1159e487217096cde0aa7a19ca4db095b19cf01f Mon Sep 17 00:00:00 2001 From: Catherine Date: Thu, 11 Jan 2024 11:47:55 +0000 Subject: [PATCH 41/70] write_verilog: emit `initial $display` correctly. --- backends/verilog/verilog_backend.cc | 24 ++++++++++++++---------- docs/source/CHAPTER_CellLib.rst | 4 +++- frontends/ast/genrtlil.cc | 2 +- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/backends/verilog/verilog_backend.cc b/backends/verilog/verilog_backend.cc index 71b9c5fd80d..1fa31e31e67 100644 --- a/backends/verilog/verilog_backend.cc +++ b/backends/verilog/verilog_backend.cc @@ -1896,17 +1896,21 @@ void dump_cell(std::ostream &f, std::string indent, RTLIL::Cell *cell) void dump_sync_print(std::ostream &f, std::string indent, const RTLIL::SigSpec &trg, const RTLIL::Const &polarity, std::vector &cells) { - f << stringf("%s" "always @(", indent.c_str()); - for (int i = 0; i < trg.size(); i++) { - if (i != 0) - f << " or "; - if (polarity[i]) - f << "posedge "; - else - f << "negedge "; - dump_sigspec(f, trg[i]); + if (trg.size() == 0) { + f << stringf("%s" "initial begin\n", indent.c_str()); + } else { + f << stringf("%s" "always @(", indent.c_str()); + for (int i = 0; i < trg.size(); i++) { + if (i != 0) + f << " or "; + if (polarity[i]) + f << "posedge "; + else + f << "negedge "; + dump_sigspec(f, trg[i]); + } + f << ") begin\n"; } - f << ") begin\n"; std::sort(cells.begin(), cells.end(), [](const RTLIL::Cell *a, const RTLIL::Cell *b) { return a->getParam(ID::PRIORITY).as_int() > b->getParam(ID::PRIORITY).as_int(); diff --git a/docs/source/CHAPTER_CellLib.rst b/docs/source/CHAPTER_CellLib.rst index 494c0651c6a..0f0d791235c 100644 --- a/docs/source/CHAPTER_CellLib.rst +++ b/docs/source/CHAPTER_CellLib.rst @@ -120,7 +120,7 @@ All binary RTL cells have two input ports ``\A`` and ``\B`` and one output port :verilog:`Y = A >>> B` $sshr :verilog:`Y = A - B` $sub :verilog:`Y = A && B` $logic_and :verilog:`Y = A * B` $mul :verilog:`Y = A || B` $logic_or :verilog:`Y = A / B` $div - :verilog:`Y = A === B` $eqx :verilog:`Y = A % B` $mod + :verilog:`Y = A === B` $eqx :verilog:`Y = A % B` $mod :verilog:`Y = A !== B` $nex ``N/A`` $divfloor :verilog:`Y = A ** B` $pow ``N/A`` $modfoor ======================= ============= ======================= ========= @@ -661,6 +661,8 @@ Ports: ``\TRG`` The signals that control when this ``$print`` cell is triggered. + If the width of this port is zero and ``\TRG_ENABLE`` is true, the cell is + triggered during initial evaluation (time zero) only. ``\EN`` Enable signal for the whole cell. diff --git a/frontends/ast/genrtlil.cc b/frontends/ast/genrtlil.cc index 0bae0f67374..0a502162e8d 100644 --- a/frontends/ast/genrtlil.cc +++ b/frontends/ast/genrtlil.cc @@ -718,7 +718,7 @@ struct AST_INTERNAL::ProcessGenerator } } cell->parameters[ID::TRG_WIDTH] = triggers.size(); - cell->parameters[ID::TRG_ENABLE] = !triggers.empty(); + cell->parameters[ID::TRG_ENABLE] = (always->type == AST_INITIAL) || !triggers.empty(); cell->parameters[ID::TRG_POLARITY] = polarity; cell->parameters[ID::PRIORITY] = --last_print_priority; cell->setPort(ID::TRG, triggers); From d49322531398208df42f175bd02cc737af314aa3 Mon Sep 17 00:00:00 2001 From: Catherine Date: Thu, 11 Jan 2024 14:10:20 +0000 Subject: [PATCH 42/70] write_cxxrtl: reset state value of comb `$print` cells. --- backends/cxxrtl/cxxrtl_backend.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index 2c35a1943e2..ac2f756d6c6 100644 --- a/backends/cxxrtl/cxxrtl_backend.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -2002,6 +2002,8 @@ struct CxxrtlWorker { } } for (auto cell : module->cells()) { + if (cell->type == ID($print) && !cell->getParam(ID::TRG_ENABLE).as_bool()) + f << indent << mangle(cell) << " = value<" << (1 + cell->getParam(ID::ARGS_WIDTH).as_int()) << ">();\n"; if (is_internal_cell(cell->type)) continue; f << indent << mangle(cell); From 7142e20dab1479987033f402607f6267b5484f20 Mon Sep 17 00:00:00 2001 From: Catherine Date: Thu, 11 Jan 2024 14:24:56 +0000 Subject: [PATCH 43/70] write_cxxrtl: support initial `$print` cells. --- backends/cxxrtl/cxxrtl_backend.cc | 49 +++++++++++++++++++------------ 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index ac2f756d6c6..ff11d7fe0b6 100644 --- a/backends/cxxrtl/cxxrtl_backend.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -1291,20 +1291,29 @@ struct CxxrtlWorker { log_assert(!for_debug); // Sync $print cells are grouped into PRINT_SYNC nodes in the FlowGraph. - log_assert(!cell->getParam(ID::TRG_ENABLE).as_bool()); + log_assert(!cell->getParam(ID::TRG_ENABLE).as_bool() || (cell->getParam(ID::TRG_ENABLE).as_bool() && cell->getParam(ID::TRG_WIDTH).as_int() == 0)); - f << indent << "auto " << mangle(cell) << "_curr = "; - dump_sigspec_rhs(cell->getPort(ID::EN)); - f << ".concat("; - dump_sigspec_rhs(cell->getPort(ID::ARGS)); - f << ").val();\n"; + if (!cell->getParam(ID::TRG_ENABLE).as_bool()) { // async $print cell + f << indent << "auto " << mangle(cell) << "_curr = "; + dump_sigspec_rhs(cell->getPort(ID::EN)); + f << ".concat("; + dump_sigspec_rhs(cell->getPort(ID::ARGS)); + f << ").val();\n"; - f << indent << "if (" << mangle(cell) << " != " << mangle(cell) << "_curr) {\n"; - inc_indent(); - dump_print(cell); - f << indent << mangle(cell) << " = " << mangle(cell) << "_curr;\n"; - dec_indent(); - f << indent << "}\n"; + f << indent << "if (" << mangle(cell) << " != " << mangle(cell) << "_curr) {\n"; + inc_indent(); + dump_print(cell); + f << indent << mangle(cell) << " = " << mangle(cell) << "_curr;\n"; + dec_indent(); + f << indent << "}\n"; + } else { // initial $print cell + f << indent << "if (!" << mangle(cell) << ") {\n"; + inc_indent(); + dump_print(cell); + f << indent << mangle(cell) << " = value<1>{1u};\n"; + dec_indent(); + f << indent << "}\n"; + } // Flip-flops } else if (is_ff_cell(cell->type)) { log_assert(!for_debug); @@ -2002,8 +2011,11 @@ struct CxxrtlWorker { } } for (auto cell : module->cells()) { + // Certain $print cells have additional state, which must be reset as well. if (cell->type == ID($print) && !cell->getParam(ID::TRG_ENABLE).as_bool()) f << indent << mangle(cell) << " = value<" << (1 + cell->getParam(ID::ARGS_WIDTH).as_int()) << ">();\n"; + if (cell->type == ID($print) && cell->getParam(ID::TRG_ENABLE).as_bool() && cell->getParam(ID::TRG_WIDTH).as_int() == 0) + f << indent << mangle(cell) << " = value<1>();\n"; if (is_internal_cell(cell->type)) continue; f << indent << mangle(cell); @@ -2432,11 +2444,11 @@ struct CxxrtlWorker { f << "\n"; bool has_cells = false; for (auto cell : module->cells()) { - if (cell->type == ID($print) && !cell->getParam(ID::TRG_ENABLE).as_bool()) { - // comb $print cell -- store the last EN/ARGS values to know when they change. - dump_attrs(cell); + // Certain $print cells have additional state, which requires storage. + if (cell->type == ID($print) && !cell->getParam(ID::TRG_ENABLE).as_bool()) f << indent << "value<" << (1 + cell->getParam(ID::ARGS_WIDTH).as_int()) << "> " << mangle(cell) << ";\n"; - } + if (cell->type == ID($print) && cell->getParam(ID::TRG_ENABLE).as_bool() && cell->getParam(ID::TRG_WIDTH).as_int() == 0) + f << indent << "value<1> " << mangle(cell) << ";\n"; if (is_internal_cell(cell->type)) continue; dump_attrs(cell); @@ -2966,8 +2978,9 @@ struct CxxrtlWorker { for (auto node : node_order) if (live_nodes[node]) { if (node->type == FlowGraph::Node::Type::CELL_EVAL && - node->cell->type == ID($print) && - node->cell->getParam(ID::TRG_ENABLE).as_bool()) + node->cell->type == ID($print) && + node->cell->getParam(ID::TRG_ENABLE).as_bool() && + node->cell->getParam(ID::TRG_WIDTH).as_int() != 0) sync_print_cells[make_pair(node->cell->getPort(ID::TRG), node->cell->getParam(ID::TRG_POLARITY))].push_back(node->cell); else schedule[module].push_back(*node); From 1eb823bd0e1daa35bd00b09177fd5be260905157 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 12 Jan 2024 00:16:23 +0000 Subject: [PATCH 44/70] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8bde98a1ce2..0a93926963c 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LDLIBS += -lrt endif endif -YOSYS_VER := 0.36+85 +YOSYS_VER := 0.36+97 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo From acf916f654574045574ab4f7d6b1cb0d8cec6f85 Mon Sep 17 00:00:00 2001 From: Dag Lem Date: Sun, 14 Jan 2024 09:08:47 +0100 Subject: [PATCH 45/70] Restore sim output from initial $display --- passes/sat/sim.cc | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/passes/sat/sim.cc b/passes/sat/sim.cc index b0753418468..542eb5c182c 100644 --- a/passes/sat/sim.cc +++ b/passes/sat/sim.cc @@ -185,6 +185,7 @@ struct SimInstance struct print_state_t { + bool initial_done; Const past_trg; Const past_en; Const past_args; @@ -350,6 +351,7 @@ struct SimInstance print.past_trg = Const(State::Sx, cell->getPort(ID::TRG).size()); print.past_args = Const(State::Sx, cell->getPort(ID::ARGS).size()); print.past_en = State::Sx; + print.initial_done = false; } } @@ -852,13 +854,14 @@ struct SimInstance bool triggered = false; Const trg = get_state(cell->getPort(ID::TRG)); + bool trg_en = cell->getParam(ID::TRG_ENABLE).as_bool(); Const en = get_state(cell->getPort(ID::EN)); Const args = get_state(cell->getPort(ID::ARGS)); if (!en.as_bool()) goto update_print; - if (cell->getParam(ID::TRG_ENABLE).as_bool()) { + if (trg.size() > 0 && trg_en) { Const trg_pol = cell->getParam(ID::TRG_POLARITY); for (int i = 0; i < trg.size(); i++) { bool pol = trg_pol[i] == State::S1; @@ -868,7 +871,12 @@ struct SimInstance if (!pol && curr == State::S0 && past == State::S1) triggered = true; } + } else if (trg_en) { + // initial $print (TRG width = 0, TRG_ENABLE = true) + if (!print.initial_done && en != print.past_en) + triggered = true; } else { + // always @(*) $print if (args != print.past_args || en != print.past_en) triggered = true; } @@ -889,6 +897,7 @@ struct SimInstance print.past_trg = trg; print.past_en = en; print.past_args = args; + print.initial_done = true; } if (check_assertions) From fac843f4801c2d7faf467ee054145baa261f6639 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 15 Jan 2024 00:17:14 +0000 Subject: [PATCH 46/70] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0a93926963c..274a93b98ec 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LDLIBS += -lrt endif endif -YOSYS_VER := 0.36+97 +YOSYS_VER := 0.36+100 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo From 5902b2826d4d96b374d1ef7d612e25b5cbb1bba4 Mon Sep 17 00:00:00 2001 From: uis Date: Sat, 11 Nov 2023 17:29:43 +0300 Subject: [PATCH 47/70] Fix printf formats --- frontends/ast/simplify.cc | 4 ++-- passes/memory/memory_libmap.cc | 6 +++--- passes/sat/recover_names.cc | 3 ++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc index 2a500b56bd3..aa5093ec751 100644 --- a/frontends/ast/simplify.cc +++ b/frontends/ast/simplify.cc @@ -204,8 +204,8 @@ void AstNode::annotateTypedEnums(AstNode *template_node) log_assert(enum_item->children[1]->type == AST_RANGE); is_signed = enum_item->children[1]->is_signed; } else { - log_error("enum_item children size==%lu, expected 1 or 2 for %s (%s)\n", - enum_item->children.size(), + log_error("enum_item children size==%zu, expected 1 or 2 for %s (%s)\n", + (size_t) enum_item->children.size(), enum_item->str.c_str(), enum_node->str.c_str() ); } diff --git a/passes/memory/memory_libmap.cc b/passes/memory/memory_libmap.cc index ec181a142c4..2e683b8eb95 100644 --- a/passes/memory/memory_libmap.cc +++ b/passes/memory/memory_libmap.cc @@ -690,7 +690,7 @@ bool apply_clock(MemConfig &cfg, const PortVariant &def, SigBit clk, bool clk_po // Perform write port assignment, validating clock options as we go. void MemMapping::assign_wr_ports() { - log_reject(stringf("Assigning write ports... (candidate configs: %lu)", cfgs.size())); + log_reject(stringf("Assigning write ports... (candidate configs: %zu)", (size_t) cfgs.size())); for (auto &port: mem.wr_ports) { if (!port.clk_enable) { // Async write ports not supported. @@ -739,7 +739,7 @@ void MemMapping::assign_wr_ports() { // Perform read port assignment, validating clock and rden options as we go. void MemMapping::assign_rd_ports() { - log_reject(stringf("Assigning read ports... (candidate configs: %lu)", cfgs.size())); + log_reject(stringf("Assigning read ports... (candidate configs: %zu)", (size_t) cfgs.size())); for (int pidx = 0; pidx < GetSize(mem.rd_ports); pidx++) { auto &port = mem.rd_ports[pidx]; MemConfigs new_cfgs; @@ -900,7 +900,7 @@ void MemMapping::assign_rd_ports() { // Validate transparency restrictions, determine where to add soft transparency logic. void MemMapping::handle_trans() { - log_reject(stringf("Handling transparency... (candidate configs: %lu)", cfgs.size())); + log_reject(stringf("Handling transparency... (candidate configs: %zu)", (size_t) cfgs.size())); if (mem.emulate_read_first_ok()) { MemConfigs new_cfgs; for (auto &cfg: cfgs) { diff --git a/passes/sat/recover_names.cc b/passes/sat/recover_names.cc index 4c30a363202..4870e2cac19 100644 --- a/passes/sat/recover_names.cc +++ b/passes/sat/recover_names.cc @@ -26,6 +26,7 @@ #include #include +#include USING_YOSYS_NAMESPACE @@ -623,7 +624,7 @@ struct RecoverNamesWorker { if (pop == 1 || pop == (8*sizeof(equiv_cls_t) - 1)) continue; - log_debug("equivalence class: %016lx\n", cls.first); + log_debug("equivalence class: %016" PRIx64 "\n", cls.first); const pool &gold_bits = cls2bits.at(cls.first).first; const pool &gate_bits = cls2bits.at(cls.first).second; if (gold_bits.empty() || gate_bits.empty()) From 568418b50b7b4d7c715f62fd2c2d5d7bd8e3a2cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Mon, 15 Jan 2024 12:35:02 +0100 Subject: [PATCH 48/70] opt_lut: Replace `-dlogic` with `-tech ice40` --- passes/opt/opt_lut.cc | 56 +++++++++++++++++------------------ techlibs/ice40/synth_ice40.cc | 2 +- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/passes/opt/opt_lut.cc b/passes/opt/opt_lut.cc index 3b079d964f4..3907285f3e6 100644 --- a/passes/opt/opt_lut.cc +++ b/passes/opt/opt_lut.cc @@ -24,6 +24,10 @@ USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN +// Type represents the following constraint: Preserve connections to dedicated +// logic cell that has ports connected to LUT inputs. This includes +// the case where both LUT and dedicated logic input are connected to the same +// constant. struct dlogic_t { IdString cell_type; // LUT input idx -> hard cell's port name @@ -515,16 +519,6 @@ struct OptLutWorker } }; -static void split(std::vector &tokens, const std::string &text, char sep) -{ - size_t start = 0, end = 0; - while ((end = text.find(sep, start)) != std::string::npos) { - tokens.push_back(text.substr(start, end - start)); - start = end + 1; - } - tokens.push_back(text.substr(start)); -} - struct OptLutPass : public Pass { OptLutPass() : Pass("opt_lut", "optimize LUT cells") { } void help() override @@ -541,6 +535,10 @@ struct OptLutPass : public Pass { log(" the case where both LUT and dedicated logic input are connected to\n"); log(" the same constant.\n"); log("\n"); + log(" -tech ice40\n"); + log(" treat the design as a LUT-mapped circuit for the iCE40 architecture\n"); + log(" and preserve connections to SB_CARRY as appropriate\n"); + log("\n"); log(" -limit N\n"); log(" only perform the first N combines, then stop. useful for debugging.\n"); log("\n"); @@ -555,28 +553,28 @@ struct OptLutPass : public Pass { size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { - if (args[argidx] == "-dlogic" && argidx+1 < args.size()) + if (args[argidx] == "-tech" && argidx+1 < args.size()) { - std::vector tokens; - split(tokens, args[++argidx], ':'); - if (tokens.size() < 2) - log_cmd_error("The -dlogic option requires at least one connection.\n"); - dlogic_t entry; - entry.cell_type = "\\" + tokens[0]; - for (auto it = tokens.begin() + 1; it != tokens.end(); ++it) { - std::vector conn_tokens; - split(conn_tokens, *it, '='); - if (conn_tokens.size() != 2) - log_cmd_error("Invalid format of -dlogic signal mapping.\n"); - IdString logic_port = "\\" + conn_tokens[0]; - int lut_input = atoi(conn_tokens[1].c_str()); - entry.lut_input_port[lut_input] = logic_port; - } - dlogic.push_back(entry); + std::string tech = args[++argidx]; + if (tech != "ice40") + log_cmd_error("Unsupported -tech argument: %s\n", tech.c_str()); + + dlogic = {{ + ID(SB_CARRY), + dict{ + std::make_pair(1, ID(I0)), + std::make_pair(2, ID(I1)), + std::make_pair(3, ID(CI)) + } + }, { + ID(SB_CARRY), + dict{ + std::make_pair(3, ID(CO)) + } + }}; continue; } - if (args[argidx] == "-limit" && argidx + 1 < args.size()) - { + if (args[argidx] == "-limit" && argidx + 1 < args.size()) { limit = atoi(args[++argidx].c_str()); continue; } diff --git a/techlibs/ice40/synth_ice40.cc b/techlibs/ice40/synth_ice40.cc index a9982649b35..4c691c7a5aa 100644 --- a/techlibs/ice40/synth_ice40.cc +++ b/techlibs/ice40/synth_ice40.cc @@ -432,7 +432,7 @@ struct SynthIce40Pass : public ScriptPass run("ice40_wrapcarry -unwrap"); run("techmap -map +/ice40/ff_map.v"); run("clean"); - run("opt_lut -dlogic SB_CARRY:I0=1:I1=2:CI=3 -dlogic SB_CARRY:CO=3"); + run("opt_lut -tech ice40"); } if (check_label("map_cells")) From fb4eeb134411cc620163855b84e5c539287337a2 Mon Sep 17 00:00:00 2001 From: "N. Engelhardt" Date: Mon, 15 Jan 2024 17:47:59 +0100 Subject: [PATCH 49/70] show: allow setting colors via selection on PROC boxes --- passes/cmds/show.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/passes/cmds/show.cc b/passes/cmds/show.cc index d57cbc6bb49..8c2695dbe7f 100644 --- a/passes/cmds/show.cc +++ b/passes/cmds/show.cc @@ -539,7 +539,7 @@ struct ShowWorker std::string proc_src = RTLIL::unescape_id(proc->name); if (proc->attributes.count(ID::src) > 0) proc_src = proc->attributes.at(ID::src).decode_string(); - fprintf(f, "p%d [shape=box, style=rounded, label=\"PROC %s\\n%s\"];\n", pidx, findLabel(proc->name.str()), proc_src.c_str()); + fprintf(f, "p%d [shape=box, style=rounded, label=\"PROC %s\\n%s\", %s];\n", pidx, findLabel(proc->name.str()), proc_src.c_str(), findColor(proc->name).c_str()); } for (auto &conn : module->connections()) From 740265bfbd593295f4886b6c6893ecb849eeecb4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 16 Jan 2024 00:16:26 +0000 Subject: [PATCH 50/70] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 274a93b98ec..f6c28adeb2c 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LDLIBS += -lrt endif endif -YOSYS_VER := 0.36+100 +YOSYS_VER := 0.36+103 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo From a5c7f69ed8f1e263f356e384ab7983ea6c7d2fe1 Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Tue, 16 Jan 2024 08:13:21 +0100 Subject: [PATCH 51/70] Release version 0.37 --- CHANGELOG | 15 +++++++++++++-- Makefile | 4 ++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4fb60904ae5..0570554cdf5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,12 +2,23 @@ List of major changes and improvements between releases ======================================================= -Yosys 0.36 .. Yosys 0.37-dev +Yosys 0.36 .. Yosys 0.37 -------------------------- + * New commands and options + - Added option "-nodisplay" to read_verilog. + + * SystemVerilog + - Correct hierarchical path names for structs and unions. + + * Various + - Print hierarchy for failed assertions in "sim" pass. + - Add "--present-only" option to "yosys-witness" to omit unused signals. + - Implement a generic record/replay interface for CXXRTL. + - Improved readability of emitted code with "write_verilog". Yosys 0.35 .. Yosys 0.36 -------------------------- -* New commands and options + * New commands and options - Added option "--" to pass arguments down to tcl when using -c option. - Added ability on MacOS and Windows to pass options after arguments on cli. - Added option "-cmp2softlogic" to synth_lattice. diff --git a/Makefile b/Makefile index f6c28adeb2c..e0e850b1193 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LDLIBS += -lrt endif endif -YOSYS_VER := 0.36+103 +YOSYS_VER := 0.37 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo @@ -157,7 +157,7 @@ endif OBJS = kernel/version_$(GIT_REV).o bumpversion: - sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline 8f07a0d.. | wc -l`/;" Makefile +# sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline 8f07a0d.. | wc -l`/;" Makefile # set 'ABCREV = default' to use abc/ as it is # From bd956d76ba0332dc433e34161e7fb2c9b89761fb Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Tue, 16 Jan 2024 08:16:07 +0100 Subject: [PATCH 52/70] Next dev cycle --- CHANGELOG | 3 +++ Makefile | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 0570554cdf5..990f9e17d75 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,9 @@ List of major changes and improvements between releases ======================================================= +Yosys 0.37 .. Yosys 0.38-dev +-------------------------- + Yosys 0.36 .. Yosys 0.37 -------------------------- * New commands and options diff --git a/Makefile b/Makefile index e0e850b1193..5428a5dafc7 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LDLIBS += -lrt endif endif -YOSYS_VER := 0.37 +YOSYS_VER := 0.37+0 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo @@ -157,7 +157,7 @@ endif OBJS = kernel/version_$(GIT_REV).o bumpversion: -# sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline 8f07a0d.. | wc -l`/;" Makefile + sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline a5c7f69.. | wc -l`/;" Makefile # set 'ABCREV = default' to use abc/ as it is # From ed81cc5f8106134e08c9aeaca74d86839945fd57 Mon Sep 17 00:00:00 2001 From: Catherine Date: Tue, 16 Jan 2024 09:22:39 +0000 Subject: [PATCH 53/70] =?UTF-8?q?cxxrtl:=20rename=20observer::{on=5Fcommit?= =?UTF-8?q?=E2=86=92on=5Fupdate}.=20(breaking=20change)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The name `on_commit` was terrible since it would not be called, as one may conclude from the name, on each `commit()`, but only whenever that method actually updates a value. --- backends/cxxrtl/runtime/cxxrtl/cxxrtl.h | 14 +++++++------- backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index 3f824722665..e2729bfe410 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -865,19 +865,19 @@ struct observer { // Called when the `commit()` method for a wire is about to update the `chunks` chunks at `base` with `chunks` chunks // at `value` that have a different bit pattern. It is guaranteed that `chunks` is equal to the wire chunk count and // `base` points to the first chunk. - virtual void on_commit(size_t chunks, const chunk_t *base, const chunk_t *value) = 0; + virtual void on_update(size_t chunks, const chunk_t *base, const chunk_t *value) = 0; // Called when the `commit()` method for a memory is about to update the `chunks` chunks at `&base[chunks * index]` // with `chunks` chunks at `value` that have a different bit pattern. It is guaranteed that `chunks` is equal to // the memory element chunk count and `base` points to the first chunk of the first element of the memory. - virtual void on_commit(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) = 0; + virtual void on_update(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) = 0; }; // The `null_observer` class has the same interface as `observer`, but has no invocation overhead, since its methods // are final and have no implementation. This allows the observer feature to be zero-cost when not in use. struct null_observer final: observer { - void on_commit(size_t chunks, const chunk_t *base, const chunk_t *value) override {} - void on_commit(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) override {} + void on_update(size_t chunks, const chunk_t *base, const chunk_t *value) override {} + void on_update(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) override {} }; template @@ -916,12 +916,12 @@ struct wire { // This method intentionally takes a mandatory argument (to make it more difficult to misuse in // black box implementations, leading to missed observer events). It is generic over its argument - // to make sure the `on_commit` call is devirtualized. This is somewhat awkward but lets us keep + // to make sure the `on_update` call is devirtualized. This is somewhat awkward but lets us keep // a single implementation for both this method and the one in `memory`. template bool commit(ObserverT &observer) { if (curr != next) { - observer.on_commit(curr.chunks, curr.data, next.data); + observer.on_update(curr.chunks, curr.data, next.data); curr = next; return true; } @@ -1003,7 +1003,7 @@ struct memory { value elem = data[entry.index]; elem = elem.update(entry.val, entry.mask); if (data[entry.index] != elem) { - observer.on_commit(value::chunks, data[0].data, elem.data, entry.index); + observer.on_update(value::chunks, data[0].data, elem.data, entry.index); changed |= true; } data[entry.index] = elem; diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h index 94f59bb0d9b..e44b1c4e10b 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h @@ -561,12 +561,12 @@ class recorder { spool::writer *writer; CXXRTL_ALWAYS_INLINE - void on_commit(size_t chunks, const chunk_t *base, const chunk_t *value) override { + void on_update(size_t chunks, const chunk_t *base, const chunk_t *value) override { writer->write_change(ident_lookup->at(base), chunks, value); } CXXRTL_ALWAYS_INLINE - void on_commit(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) override { + void on_update(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) override { writer->write_change(ident_lookup->at(base), chunks, value, index); } } record_observer; From bf1a99da09b2db6eb8a44e324de48f8e4092e9a1 Mon Sep 17 00:00:00 2001 From: Catherine Date: Tue, 16 Jan 2024 09:53:43 +0000 Subject: [PATCH 54/70] cxxrtl: make observer methods non-virtual. (breaking change) This avoids having to devirtualize them later to get performance back, and simplifies the code a bit. The change is prompted by the desire to add a similar observer object to `eval()`, and a repeated consideration of whether the dispatch on these should be virtual in first place. --- backends/cxxrtl/cxxrtl_backend.cc | 4 ++-- backends/cxxrtl/runtime/cxxrtl/cxxrtl.h | 14 +++----------- backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h | 10 ++++------ 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index ff11d7fe0b6..30995ea11b0 100644 --- a/backends/cxxrtl/cxxrtl_backend.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -2391,7 +2391,7 @@ struct CxxrtlWorker { f << indent << "}\n"; f << "\n"; f << indent << "bool commit() override {\n"; - f << indent << indent << "null_observer observer;\n"; + f << indent << indent << "observer observer;\n"; f << indent << indent << "return commit<>(observer);\n"; f << indent << "}\n"; if (debug_info) { @@ -2486,7 +2486,7 @@ struct CxxrtlWorker { f << indent << "}\n"; f << "\n"; f << indent << "bool commit() override {\n"; - f << indent << indent << "null_observer observer;\n"; + f << indent << indent << "observer observer;\n"; f << indent << indent << "return commit<>(observer);\n"; f << indent << "}\n"; if (debug_info) { diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index e2729bfe410..bd336f481aa 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -865,19 +865,12 @@ struct observer { // Called when the `commit()` method for a wire is about to update the `chunks` chunks at `base` with `chunks` chunks // at `value` that have a different bit pattern. It is guaranteed that `chunks` is equal to the wire chunk count and // `base` points to the first chunk. - virtual void on_update(size_t chunks, const chunk_t *base, const chunk_t *value) = 0; + void on_update(size_t chunks, const chunk_t *base, const chunk_t *value) {} // Called when the `commit()` method for a memory is about to update the `chunks` chunks at `&base[chunks * index]` // with `chunks` chunks at `value` that have a different bit pattern. It is guaranteed that `chunks` is equal to // the memory element chunk count and `base` points to the first chunk of the first element of the memory. - virtual void on_update(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) = 0; -}; - -// The `null_observer` class has the same interface as `observer`, but has no invocation overhead, since its methods -// are final and have no implementation. This allows the observer feature to be zero-cost when not in use. -struct null_observer final: observer { - void on_update(size_t chunks, const chunk_t *base, const chunk_t *value) override {} - void on_update(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) override {} + void on_update(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) {} }; template @@ -916,8 +909,7 @@ struct wire { // This method intentionally takes a mandatory argument (to make it more difficult to misuse in // black box implementations, leading to missed observer events). It is generic over its argument - // to make sure the `on_update` call is devirtualized. This is somewhat awkward but lets us keep - // a single implementation for both this method and the one in `memory`. + // to allow the `on_update` method to be non-virtual. template bool commit(ObserverT &observer) { if (curr != next) { diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h index e44b1c4e10b..d824fdb83c8 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h @@ -556,22 +556,20 @@ class recorder { bool record_incremental(ModuleT &module) { assert(streaming); - struct : public observer { + struct { std::unordered_map *ident_lookup; spool::writer *writer; CXXRTL_ALWAYS_INLINE - void on_update(size_t chunks, const chunk_t *base, const chunk_t *value) override { + void on_update(size_t chunks, const chunk_t *base, const chunk_t *value) { writer->write_change(ident_lookup->at(base), chunks, value); } CXXRTL_ALWAYS_INLINE - void on_update(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) override { + void on_update(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) { writer->write_change(ident_lookup->at(base), chunks, value, index); } - } record_observer; - record_observer.ident_lookup = &ident_lookup; - record_observer.writer = &writer; + } record_observer = { &ident_lookup, &writer }; writer.write_sample(/*incremental=*/true, pointer++, timestamp); for (auto input_index : inputs) { From a33acb7cd9a2366df55036db039b01b72efcc60b Mon Sep 17 00:00:00 2001 From: Catherine Date: Tue, 16 Jan 2024 09:08:07 +0000 Subject: [PATCH 55/70] cxxrtl: refactor the formatter and use a closure. This commit achieves three roughly equally important goals: 1. To bring the rendering code in kernel/fmt.cc and in cxxrtl.h as close together as possible, with an ideal of only having the bigint library as the difference between the render functions. 2. To make the treatment of `$time` and `$realtime` in CXXRTL closer to the Verilog semantics, at least in the formatting code. 3. To change the code generator so that all of the `$print`-to-`string` conversion code is contained inside of a closure. There are two reasons to aim for goal (3): a. Because output redirection through definition of a global ostream object is neither convenient nor useful for environments where the output is consumed by other code rather than being printed on a terminal. b. Because it may be desirable to, in some cases, ignore the `$print` cells that are present in the netlist based on a runtime decision. This is doubly true for an upcoming `$check` cell implementing assertions, since failing a `$check` would by default cause a crash. --- backends/cxxrtl/cxxrtl_backend.cc | 9 +- backends/cxxrtl/runtime/cxxrtl/cxxrtl.h | 210 ++++++++++++++---------- kernel/fmt.cc | 120 ++++++-------- kernel/fmt.h | 5 +- tests/fmt/always_full_tb.cc | 7 +- 5 files changed, 185 insertions(+), 166 deletions(-) diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index 30995ea11b0..e4a0006272d 100644 --- a/backends/cxxrtl/cxxrtl_backend.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -1072,9 +1072,12 @@ struct CxxrtlWorker { dump_sigspec_rhs(cell->getPort(ID::EN)); f << " == value<1>{1u}) {\n"; inc_indent(); - f << indent << print_output; - fmt.emit_cxxrtl(f, [this](const RTLIL::SigSpec &sig) { dump_sigspec_rhs(sig); }); - f << ";\n"; + f << indent << "auto formatter = [&](int64_t itime, double ftime) {\n"; + inc_indent(); + fmt.emit_cxxrtl(f, indent, [this](const RTLIL::SigSpec &sig) { dump_sigspec_rhs(sig); }); + dec_indent(); + f << indent << "};\n"; + f << indent << print_output << " << formatter(steps, steps);\n"; dec_indent(); f << indent << "}\n"; } diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index bd336f481aa..654f13b4a36 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -565,7 +565,7 @@ struct value : public expr_base> { } value neg() const { - return value { 0u }.sub(*this); + return value().sub(*this); } bool ucmp(const value &other) const { @@ -763,101 +763,133 @@ std::ostream &operator<<(std::ostream &os, const value &val) { return os; } -template -struct value_formatted { - const value &val; - bool character; - bool justify_left; - char padding; - int width; - int base; - bool signed_; - bool plus; - - value_formatted(const value &val, bool character, bool justify_left, char padding, int width, int base, bool signed_, bool plus) : - val(val), character(character), justify_left(justify_left), padding(padding), width(width), base(base), signed_(signed_), plus(plus) {} - value_formatted(const value_formatted &) = delete; - value_formatted &operator=(const value_formatted &rhs) = delete; -}; - -template -std::ostream &operator<<(std::ostream &os, const value_formatted &vf) -{ - value val = vf.val; - - std::string buf; - - // We might want to replace some of these bit() calls with direct - // chunk access if it turns out to be slow enough to matter. - - if (!vf.character) { - size_t width = Bits; - if (vf.base != 10) { - width = 0; - for (size_t index = 0; index < Bits; index++) - if (val.bit(index)) - width = index + 1; - } +// Must be kept in sync with `struct FmtPart` in kernel/fmt.h! +// Default member initializers would make this a non-aggregate-type in C++11, so they are commented out. +struct fmt_part { + enum { + STRING = 0, + INTEGER = 1, + CHARACTER = 2, + TIME = 3, + } type; + + // STRING type + std::string str; + + // INTEGER/CHARACTER types + // + value val; + + // INTEGER/CHARACTER/TIME types + enum { + RIGHT = 0, + LEFT = 1, + } justify; // = RIGHT; + char padding; // = '\0'; + size_t width; // = 0; + + // INTEGER type + unsigned base; // = 10; + bool signed_; // = false; + bool plus; // = false; + + // TIME type + bool realtime; // = false; + // + int64_t itime; + // + double ftime; + + // Format the part as a string. + // + // The values of `itime` and `ftime` are used for `$time` and `$realtime`, correspondingly. + template + std::string render(value val, int64_t itime, double ftime) + { + // We might want to replace some of these bit() calls with direct + // chunk access if it turns out to be slow enough to matter. + std::string buf; + switch (type) { + case STRING: + return str; + + case CHARACTER: { + buf.reserve(Bits/8); + for (int i = 0; i < Bits; i += 8) { + char ch = 0; + for (int j = 0; j < 8 && i + j < int(Bits); j++) + if (val.bit(i + j)) + ch |= 1 << j; + if (ch != 0) + buf.append({ch}); + } + std::reverse(buf.begin(), buf.end()); + break; + } - if (vf.base == 2) { - for (size_t i = width; i > 0; i--) - buf += (val.bit(i - 1) ? '1' : '0'); - } else if (vf.base == 8 || vf.base == 16) { - size_t step = (vf.base == 16) ? 4 : 3; - for (size_t index = 0; index < width; index += step) { - uint8_t value = val.bit(index) | (val.bit(index + 1) << 1) | (val.bit(index + 2) << 2); - if (step == 4) - value |= val.bit(index + 3) << 3; - buf += "0123456789abcdef"[value]; + case INTEGER: { + size_t width = Bits; + if (base != 10) { + width = 0; + for (size_t index = 0; index < Bits; index++) + if (val.bit(index)) + width = index + 1; + } + + if (base == 2) { + for (size_t i = width; i > 0; i--) + buf += (val.bit(i - 1) ? '1' : '0'); + } else if (base == 8 || base == 16) { + size_t step = (base == 16) ? 4 : 3; + for (size_t index = 0; index < width; index += step) { + uint8_t value = val.bit(index) | (val.bit(index + 1) << 1) | (val.bit(index + 2) << 2); + if (step == 4) + value |= val.bit(index + 3) << 3; + buf += "0123456789abcdef"[value]; + } + std::reverse(buf.begin(), buf.end()); + } else if (base == 10) { + bool negative = signed_ && val.is_neg(); + if (negative) + val = val.neg(); + if (val.is_zero()) + buf += '0'; + value<(Bits > 4 ? Bits : 4)> xval = val.template zext<(Bits > 4 ? Bits : 4)>(); + while (!xval.is_zero()) { + value<(Bits > 4 ? Bits : 4)> quotient, remainder; + if (Bits >= 4) + std::tie(quotient, remainder) = xval.udivmod(value<(Bits > 4 ? Bits : 4)>{10u}); + else + std::tie(quotient, remainder) = std::make_pair(value<(Bits > 4 ? Bits : 4)>{0u}, xval); + buf += '0' + remainder.template trunc<4>().template get(); + xval = quotient; + } + if (negative || plus) + buf += negative ? '-' : '+'; + std::reverse(buf.begin(), buf.end()); + } else assert(false && "Unsupported base for fmt_part"); + break; } - std::reverse(buf.begin(), buf.end()); - } else if (vf.base == 10) { - bool negative = vf.signed_ && val.is_neg(); - if (negative) - val = val.neg(); - if (val.is_zero()) - buf += '0'; - while (!val.is_zero()) { - value quotient, remainder; - if (Bits >= 4) - std::tie(quotient, remainder) = val.udivmod(value{10u}); - else - std::tie(quotient, remainder) = std::make_pair(value{0u}, val); - buf += '0' + remainder.template trunc<(Bits > 4 ? 4 : Bits)>().val().template get(); - val = quotient; + + case TIME: { + buf = realtime ? std::to_string(ftime) : std::to_string(itime); + break; } - if (negative || vf.plus) - buf += negative ? '-' : '+'; - std::reverse(buf.begin(), buf.end()); - } else assert(false); - } else { - buf.reserve(Bits/8); - for (int i = 0; i < Bits; i += 8) { - char ch = 0; - for (int j = 0; j < 8 && i + j < int(Bits); j++) - if (val.bit(i + j)) - ch |= 1 << j; - if (ch != 0) - buf.append({ch}); } - std::reverse(buf.begin(), buf.end()); - } - assert(vf.width == 0 || vf.padding != '\0'); - if (!vf.justify_left && buf.size() < vf.width) { - size_t pad_width = vf.width - buf.size(); - if (vf.padding == '0' && (buf.front() == '+' || buf.front() == '-')) { - os << buf.front(); - buf.erase(0, 1); + std::string str; + assert(width == 0 || padding != '\0'); + if (justify == RIGHT && buf.size() < width) { + size_t pad_width = width - buf.size(); + if (padding == '0' && (buf.front() == '+' || buf.front() == '-')) { + str += buf.front(); + buf.erase(0, 1); + } + str += std::string(pad_width, padding); } - os << std::string(pad_width, vf.padding); + str += buf; + if (justify == LEFT && buf.size() < width) + str += std::string(width - buf.size(), padding); + return str; } - os << buf; - if (vf.justify_left && buf.size() < vf.width) - os << std::string(vf.width - buf.size(), vf.padding); - - return os; -} +}; // An object that can be passed to a `commit()` method in order to produce a replay log of every state change in // the simulation. diff --git a/kernel/fmt.cc b/kernel/fmt.cc index 383fa7de16f..7d16b20c1d5 100644 --- a/kernel/fmt.cc +++ b/kernel/fmt.cc @@ -569,82 +569,60 @@ std::vector Fmt::emit_verilog() const return args; } -void Fmt::emit_cxxrtl(std::ostream &f, std::function emit_sig) const +std::string escape_cxx_string(const std::string &input) { + std::string output = "\""; + for (auto c : input) { + if (::isprint(c)) { + if (c == '\\') + output.push_back('\\'); + output.push_back(c); + } else { + char l = c & 0xf, h = (c >> 4) & 0xf; + output.append("\\x"); + output.push_back((h < 10 ? '0' + h : 'a' + h - 10)); + output.push_back((l < 10 ? '0' + l : 'a' + l - 10)); + } + } + output.push_back('"'); + if (output.find('\0') != std::string::npos) { + output.insert(0, "std::string {"); + output.append(stringf(", %zu}", input.size())); + } + return output; +} + +void Fmt::emit_cxxrtl(std::ostream &os, std::string indent, std::function emit_sig) const +{ + os << indent << "std::string buf;\n"; for (auto &part : parts) { + os << indent << "buf += fmt_part { "; + os << "fmt_part::"; switch (part.type) { - case FmtPart::STRING: - f << " << \""; - for (char c : part.str) { - switch (c) { - case '\\': - YS_FALLTHROUGH - case '"': - f << '\\' << c; - break; - case '\a': - f << "\\a"; - break; - case '\b': - f << "\\b"; - break; - case '\f': - f << "\\f"; - break; - case '\n': - f << "\\n"; - break; - case '\r': - f << "\\r"; - break; - case '\t': - f << "\\t"; - break; - case '\v': - f << "\\v"; - break; - default: - f << c; - break; - } - } - f << '"'; - break; - - case FmtPart::INTEGER: - case FmtPart::CHARACTER: { - f << " << value_formatted<" << part.sig.size() << ">("; - emit_sig(part.sig); - f << ", " << (part.type == FmtPart::CHARACTER); - f << ", " << (part.justify == FmtPart::LEFT); - f << ", (char)" << (int)part.padding; - f << ", " << part.width; - f << ", " << part.base; - f << ", " << part.signed_; - f << ", " << part.plus; - f << ')'; - break; - } - - case FmtPart::TIME: { - // CXXRTL only records steps taken, so there's no difference between - // the values taken by $time and $realtime. - f << " << value_formatted<64>("; - f << "value<64>{steps}"; - f << ", " << (part.type == FmtPart::CHARACTER); - f << ", " << (part.justify == FmtPart::LEFT); - f << ", (char)" << (int)part.padding; - f << ", " << part.width; - f << ", " << part.base; - f << ", " << part.signed_; - f << ", " << part.plus; - f << ')'; - break; - } - - default: log_abort(); + case FmtPart::STRING: os << "STRING"; break; + case FmtPart::INTEGER: os << "INTEGER"; break; + case FmtPart::CHARACTER: os << "CHARACTER"; break; + case FmtPart::TIME: os << "TIME"; break; + } + os << ", "; + os << escape_cxx_string(part.str) << ", "; + os << "fmt_part::"; + switch (part.justify) { + case FmtPart::LEFT: os << "LEFT"; break; + case FmtPart::RIGHT: os << "RIGHT"; break; } + os << ", "; + os << "(char)" << (int)part.padding << ", "; + os << part.width << ", "; + os << part.base << ", "; + os << part.signed_ << ", "; + os << part.plus << ", "; + os << part.realtime; + os << " }.render("; + emit_sig(part.sig); + os << ", itime, ftime);\n"; } + os << indent << "return buf;\n"; } std::string Fmt::render() const diff --git a/kernel/fmt.h b/kernel/fmt.h index 39a278c562e..126d020c016 100644 --- a/kernel/fmt.h +++ b/kernel/fmt.h @@ -50,6 +50,7 @@ struct VerilogFmtArg { // RTLIL format part, such as the substitutions in: // "foo {4:> 4du} bar {2:<01hs}" +// Must be kept in sync with `struct fmt_part` in backends/cxxrtl/runtime/cxxrtl/cxxrtl.h! struct FmtPart { enum { STRING = 0, @@ -71,7 +72,7 @@ struct FmtPart { } justify = RIGHT; char padding = '\0'; size_t width = 0; - + // INTEGER type unsigned base = 10; bool signed_ = false; @@ -93,7 +94,7 @@ struct Fmt { void parse_verilog(const std::vector &args, bool sformat_like, int default_base, RTLIL::IdString task_name, RTLIL::IdString module_name); std::vector emit_verilog() const; - void emit_cxxrtl(std::ostream &f, std::function emit_sig) const; + void emit_cxxrtl(std::ostream &os, std::string indent, std::function emit_sig) const; std::string render() const; diff --git a/tests/fmt/always_full_tb.cc b/tests/fmt/always_full_tb.cc index 229f78aebe1..a29a8ae05bc 100644 --- a/tests/fmt/always_full_tb.cc +++ b/tests/fmt/always_full_tb.cc @@ -2,8 +2,13 @@ int main() { + struct : public performer { + int64_t time() const override { return 1; } + void on_print(const std::string &output, const cxxrtl::metadata_map &) override { std::cerr << output; } + } performer; + cxxrtl_design::p_always__full uut; uut.p_clk.set(!uut.p_clk); - uut.step(); + uut.step(&performer); return 0; } From 02e3d508fad9c07692022555c8cefd76575d8c98 Mon Sep 17 00:00:00 2001 From: Catherine Date: Tue, 16 Jan 2024 10:45:22 +0000 Subject: [PATCH 56/70] cxxrtl: remove `module::steps`. (breaking change) This approach to tracking simulation time was a mistake that I did not catch in review. It has several issues: 1. There is absolutely no requirement to call `step()`, as it is a convenience function. In particular, `steps` will not be incremented in submodules if `-noflatten` is used. 2. The semantics of `steps` does not match that of the Verilog `$time` construct. 3. There is no way to make the semantics of `%t` match that of Verilog. 4. The `module` interface is intentionally very barebones. It is little more than a container for three method pointers, `reset`, `eval`, and `commit`. Adding ancillary data to it goes against this. If similar functionality is introduced again it should probably be a variable that is global per toplevel design using some object that is unique for an entire hierarchy of modules, and ideally exposed via the C API. For now, it is being removed (in this commit) and (in next commit) the capability is being reintroduced through a context object that can be specified for `eval()`. --- backends/cxxrtl/cxxrtl_backend.cc | 2 +- backends/cxxrtl/runtime/cxxrtl/cxxrtl.h | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index e4a0006272d..3b0c2827df5 100644 --- a/backends/cxxrtl/cxxrtl_backend.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -1077,7 +1077,7 @@ struct CxxrtlWorker { fmt.emit_cxxrtl(f, indent, [this](const RTLIL::SigSpec &sig) { dump_sigspec_rhs(sig); }); dec_indent(); f << indent << "};\n"; - f << indent << print_output << " << formatter(steps, steps);\n"; + f << indent << print_output << " << formatter(0, 0.0);\n"; dec_indent(); f << indent << "}\n"; } diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index 654f13b4a36..7d6a75d4a71 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -1331,10 +1331,7 @@ struct module { virtual bool eval() = 0; virtual bool commit() = 0; - unsigned int steps = 0; - size_t step() { - ++steps; size_t deltas = 0; bool converged = false; do { From 905f07c03fedefd2be6333ab5425b2623955630f Mon Sep 17 00:00:00 2001 From: Catherine Date: Tue, 16 Jan 2024 11:12:32 +0000 Subject: [PATCH 57/70] cxxrtl: introduce `performer`, a context object for `eval()`. (breaking change) At the moment the only thing it allows is redirecting `$print` cell output in a context-dependent manner. In the future, it will allow customizing handling of `$check` cells (where the default is to abort), of out-of-range memory accesses, and other runtime conditions with effects. This context object also allows a suitably written testbench to add Verilog-compliant `$time`/`$realtime` handling, albeit it involves the ceremony of defining a `performer` subclass. Most people will want something like this to customize `$time`: int64_t time = 0; struct : public performer { int64_t *time_ptr; int64_t time() const override { return *time_ptr; } } performer = { &time }; p_top.step(&performer); --- backends/cxxrtl/cxxrtl_backend.cc | 34 ++++++++++++++----------- backends/cxxrtl/runtime/cxxrtl/cxxrtl.h | 24 +++++++++++++---- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index 3b0c2827df5..c6a4265fcbf 100644 --- a/backends/cxxrtl/cxxrtl_backend.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -1077,7 +1077,15 @@ struct CxxrtlWorker { fmt.emit_cxxrtl(f, indent, [this](const RTLIL::SigSpec &sig) { dump_sigspec_rhs(sig); }); dec_indent(); f << indent << "};\n"; - f << indent << print_output << " << formatter(0, 0.0);\n"; + f << indent << "if (performer) {\n"; + inc_indent(); + f << indent << "performer->on_print(formatter(performer->time(), performer->realtime()));\n"; + dec_indent(); + f << indent << "} else {\n"; + inc_indent(); + f << indent << print_output << " << formatter(0, 0.0);\n"; + dec_indent(); + f << indent << "}\n"; dec_indent(); f << indent << "}\n"; } @@ -1497,11 +1505,11 @@ struct CxxrtlWorker { }; if (buffered_inputs) { // If we have any buffered inputs, there's no chance of converging immediately. - f << indent << mangle(cell) << access << "eval();\n"; + f << indent << mangle(cell) << access << "eval(performer);\n"; f << indent << "converged = false;\n"; assign_from_outputs(/*cell_converged=*/false); } else { - f << indent << "if (" << mangle(cell) << access << "eval()) {\n"; + f << indent << "if (" << mangle(cell) << access << "eval(performer)) {\n"; inc_indent(); assign_from_outputs(/*cell_converged=*/true); dec_indent(); @@ -2384,7 +2392,8 @@ struct CxxrtlWorker { dump_reset_method(module); f << indent << "}\n"; f << "\n"; - f << indent << "bool eval() override {\n"; + // No default argument, to prevent unintentional `return bb_foo::eval();` calls that drop performer. + f << indent << "bool eval(performer *performer) override {\n"; dump_eval_method(module); f << indent << "}\n"; f << "\n"; @@ -2481,7 +2490,7 @@ struct CxxrtlWorker { f << "\n"; f << indent << "void reset() override;\n"; f << "\n"; - f << indent << "bool eval() override;\n"; + f << indent << "bool eval(performer *performer = nullptr) override;\n"; f << "\n"; f << indent << "template\n"; f << indent << "bool commit(ObserverT &observer) {\n"; @@ -2520,7 +2529,7 @@ struct CxxrtlWorker { dump_reset_method(module); f << indent << "}\n"; f << "\n"; - f << indent << "bool " << mangle(module) << "::eval() {\n"; + f << indent << "bool " << mangle(module) << "::eval(performer *performer) {\n"; dump_eval_method(module); f << indent << "}\n"; if (debug_info) { @@ -2544,7 +2553,6 @@ struct CxxrtlWorker { RTLIL::Module *top_module = nullptr; std::vector modules; TopoSort topo_design; - bool has_prints = false; for (auto module : design->modules()) { if (!design->selected_module(module)) continue; @@ -2557,8 +2565,6 @@ struct CxxrtlWorker { topo_design.node(module); for (auto cell : module->cells()) { - if (cell->type == ID($print)) - has_prints = true; if (is_internal_cell(cell->type) || is_cxxrtl_blackbox_cell(cell)) continue; RTLIL::Module *cell_module = design->module(cell->type); @@ -2617,8 +2623,6 @@ struct CxxrtlWorker { f << "#include \"" << basename(intf_filename) << "\"\n"; else f << "#include \n"; - if (has_prints) - f << "#include \n"; f << "\n"; f << "#if defined(CXXRTL_INCLUDE_CAPI_IMPL) || \\\n"; f << " defined(CXXRTL_INCLUDE_VCD_CAPI_IMPL)\n"; @@ -3296,7 +3300,7 @@ struct CxxrtlBackend : public Backend { log(" value<8> p_i_data;\n"); log(" wire<8> p_o_data;\n"); log("\n"); - log(" bool eval() override;\n"); + log(" bool eval(performer *performer) override;\n"); log(" template\n"); log(" bool commit(ObserverT &observer);\n"); log(" bool commit() override;\n"); @@ -3311,11 +3315,11 @@ struct CxxrtlBackend : public Backend { log(" namespace cxxrtl_design {\n"); log("\n"); log(" struct stderr_debug : public bb_p_debug {\n"); - log(" bool eval() override {\n"); + log(" bool eval(performer *performer) override {\n"); log(" if (posedge_p_clk() && p_en)\n"); log(" fprintf(stderr, \"debug: %%02x\\n\", p_i_data.data[0]);\n"); log(" p_o_data.next = p_i_data;\n"); - log(" return bb_p_debug::eval();\n"); + log(" return bb_p_debug::eval(performer);\n"); log(" }\n"); log(" };\n"); log("\n"); @@ -3416,7 +3420,7 @@ struct CxxrtlBackend : public Backend { log(" -print-output \n"); log(" $print cells in the generated code direct their output to .\n"); log(" must be one of \"std::cout\", \"std::cerr\". if not specified,\n"); - log(" \"std::cout\" is used.\n"); + log(" \"std::cout\" is used. explicitly provided performer overrides this.\n"); log("\n"); log(" -nohierarchy\n"); log(" use design hierarchy as-is. in most designs, a top module should be\n"); diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index 7d6a75d4a71..d15772a3cc2 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -39,6 +39,7 @@ #include #include #include +#include // `cxxrtl::debug_item` has to inherit from `cxxrtl_object` to satisfy strict aliasing requirements. #include @@ -891,8 +892,21 @@ struct fmt_part { } }; +// An object that can be passed to a `eval()` method in order to act on side effects. +struct performer { + // Called to evaluate a Verilog `$time` expression. + virtual int64_t time() const { return 0; } + + // Called to evaluate a Verilog `$realtime` expression. + virtual double realtime() const { return time(); } + + // Called when a `$print` cell is triggered. + virtual void on_print(const std::string &output) { std::cout << output; } +}; + // An object that can be passed to a `commit()` method in order to produce a replay log of every state change in -// the simulation. +// the simulation. Unlike `performer`, `observer` does not use virtual calls as their overhead is unacceptable, and +// a comparatively heavyweight template-based solution is justified. struct observer { // Called when the `commit()` method for a wire is about to update the `chunks` chunks at `base` with `chunks` chunks // at `value` that have a different bit pattern. It is guaranteed that `chunks` is equal to the wire chunk count and @@ -1328,14 +1342,14 @@ struct module { virtual void reset() = 0; - virtual bool eval() = 0; - virtual bool commit() = 0; + virtual bool eval(performer *performer = nullptr) = 0; + virtual bool commit() = 0; // commit observer isn't available since it avoids virtual calls - size_t step() { + size_t step(performer *performer = nullptr) { size_t deltas = 0; bool converged = false; do { - converged = eval(); + converged = eval(performer); deltas++; } while (commit() && !converged); return deltas; From 5a1fcdea137585ecaffb602dcd1fa324984cce0a Mon Sep 17 00:00:00 2001 From: Catherine Date: Tue, 16 Jan 2024 16:17:10 +0000 Subject: [PATCH 58/70] cxxrtl: include attributes in `performer::on_print` callback. This is useful primarily to determine the source location, but can also be used for any other purpose. --- backends/cxxrtl/cxxrtl_backend.cc | 5 ++- backends/cxxrtl/runtime/cxxrtl/cxxrtl.h | 54 ++++++++++++------------- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index c6a4265fcbf..ef718067f25 100644 --- a/backends/cxxrtl/cxxrtl_backend.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -1079,7 +1079,10 @@ struct CxxrtlWorker { f << indent << "};\n"; f << indent << "if (performer) {\n"; inc_indent(); - f << indent << "performer->on_print(formatter(performer->time(), performer->realtime()));\n"; + f << indent << "static const metadata_map attributes = "; + dump_metadata_map(cell->attributes); + f << ";\n"; + f << indent << "performer->on_print(formatter(performer->time(), performer->realtime()), attributes);\n"; dec_indent(); f << indent << "} else {\n"; inc_indent(); diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index d15772a3cc2..d3b9cf9dbbc 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -892,33 +892,6 @@ struct fmt_part { } }; -// An object that can be passed to a `eval()` method in order to act on side effects. -struct performer { - // Called to evaluate a Verilog `$time` expression. - virtual int64_t time() const { return 0; } - - // Called to evaluate a Verilog `$realtime` expression. - virtual double realtime() const { return time(); } - - // Called when a `$print` cell is triggered. - virtual void on_print(const std::string &output) { std::cout << output; } -}; - -// An object that can be passed to a `commit()` method in order to produce a replay log of every state change in -// the simulation. Unlike `performer`, `observer` does not use virtual calls as their overhead is unacceptable, and -// a comparatively heavyweight template-based solution is justified. -struct observer { - // Called when the `commit()` method for a wire is about to update the `chunks` chunks at `base` with `chunks` chunks - // at `value` that have a different bit pattern. It is guaranteed that `chunks` is equal to the wire chunk count and - // `base` points to the first chunk. - void on_update(size_t chunks, const chunk_t *base, const chunk_t *value) {} - - // Called when the `commit()` method for a memory is about to update the `chunks` chunks at `&base[chunks * index]` - // with `chunks` chunks at `value` that have a different bit pattern. It is guaranteed that `chunks` is equal to - // the memory element chunk count and `base` points to the first chunk of the first element of the memory. - void on_update(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) {} -}; - template struct wire { static constexpr size_t bits = Bits; @@ -1100,6 +1073,33 @@ struct metadata { typedef std::map metadata_map; +// An object that can be passed to a `eval()` method in order to act on side effects. +struct performer { + // Called to evaluate a Verilog `$time` expression. + virtual int64_t time() const { return 0; } + + // Called to evaluate a Verilog `$realtime` expression. + virtual double realtime() const { return time(); } + + // Called when a `$print` cell is triggered. + virtual void on_print(const std::string &output, const metadata_map &attributes) { std::cout << output; } +}; + +// An object that can be passed to a `commit()` method in order to produce a replay log of every state change in +// the simulation. Unlike `performer`, `observer` does not use virtual calls as their overhead is unacceptable, and +// a comparatively heavyweight template-based solution is justified. +struct observer { + // Called when the `commit()` method for a wire is about to update the `chunks` chunks at `base` with `chunks` chunks + // at `value` that have a different bit pattern. It is guaranteed that `chunks` is equal to the wire chunk count and + // `base` points to the first chunk. + void on_update(size_t chunks, const chunk_t *base, const chunk_t *value) {} + + // Called when the `commit()` method for a memory is about to update the `chunks` chunks at `&base[chunks * index]` + // with `chunks` chunks at `value` that have a different bit pattern. It is guaranteed that `chunks` is equal to + // the memory element chunk count and `base` points to the first chunk of the first element of the memory. + void on_update(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) {} +}; + // Tag class to disambiguate values/wires and their aliases. struct debug_alias {}; From 37a6c9a097ddb7e3fff6463b6e4f2d26f671f710 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 17 Jan 2024 00:16:14 +0000 Subject: [PATCH 59/70] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5428a5dafc7..a3447e005ba 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LDLIBS += -lrt endif endif -YOSYS_VER := 0.37+0 +YOSYS_VER := 0.37+1 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo From 1764c0ee3c6db3cdc07f78dbdc3d24d3a9cf61a5 Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Thu, 18 Jan 2024 08:47:04 +0100 Subject: [PATCH 60/70] Fix verific clocking when no driver exist --- frontends/verific/verific.cc | 2 +- tests/verific/clocking.ys | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 tests/verific/clocking.ys diff --git a/frontends/verific/verific.cc b/frontends/verific/verific.cc index 9737fde89c2..adabd2700aa 100644 --- a/frontends/verific/verific.cc +++ b/frontends/verific/verific.cc @@ -2110,7 +2110,7 @@ VerificClocking::VerificClocking(VerificImporter *importer, Net *net, bool sva_a if (sva_at_only) do { Instance *inst_mux = net->Driver(); - if (inst_mux->Type() != PRIM_MUX) + if (inst_mux == nullptr || inst_mux->Type() != PRIM_MUX) break; bool pwr1 = inst_mux->GetInput1()->IsPwr(); diff --git a/tests/verific/clocking.ys b/tests/verific/clocking.ys new file mode 100644 index 00000000000..bfdbeb748c1 --- /dev/null +++ b/tests/verific/clocking.ys @@ -0,0 +1,10 @@ +read -sv < Date: Fri, 19 Jan 2024 00:16:38 +0000 Subject: [PATCH 61/70] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a3447e005ba..f2af053be44 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LDLIBS += -lrt endif endif -YOSYS_VER := 0.37+1 +YOSYS_VER := 0.37+15 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo From ae991abf2e4a4074a32091b2b6f1eea81dd68514 Mon Sep 17 00:00:00 2001 From: YRabbit Date: Fri, 19 Jan 2024 15:26:37 +1000 Subject: [PATCH 62/70] gowin: fix the BRAM mapping. The primitives used have been corrected and changes have been made to the set of signals. The empirically established need to set the OCEx signal to 1 when using READ_MODE=0 is reflected. Signed-off-by: YRabbit --- techlibs/gowin/brams_map.v | 52 ++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/techlibs/gowin/brams_map.v b/techlibs/gowin/brams_map.v index 7ffc91bacc1..5e1299c2812 100644 --- a/techlibs/gowin/brams_map.v +++ b/techlibs/gowin/brams_map.v @@ -131,7 +131,7 @@ if (PORT_A_WIDTH < 9) begin .CE(PORT_A_CLK_EN), .WRE(WRE), .RESET(RST), - .OCE(1'b0), + .OCE(1'b1), .AD(AD), .DI(DI), .DO(DO), @@ -157,7 +157,7 @@ end else begin .CE(PORT_A_CLK_EN), .WRE(WRE), .RESET(RST), - .OCE(1'b0), + .OCE(1'b1), .AD(AD), .DI(DI), .DO(DO), @@ -224,7 +224,7 @@ if (PORT_A_WIDTH < 9 || PORT_B_WIDTH < 9) begin assign PORT_A_RD_DATA = `x8_rd_data(DOA); assign PORT_B_RD_DATA = `x8_rd_data(DOB); - DP #( + DPB #( `INIT(init_slice_x8) .READ_MODE0(1'b0), .READ_MODE1(1'b0), @@ -232,16 +232,18 @@ if (PORT_A_WIDTH < 9 || PORT_B_WIDTH < 9) begin .WRITE_MODE1(PORT_B_OPTION_WRITE_MODE), .BIT_WIDTH_0(`x8_width(PORT_A_WIDTH)), .BIT_WIDTH_1(`x8_width(PORT_B_WIDTH)), - .BLK_SEL(3'b000), + .BLK_SEL_0(3'b000), + .BLK_SEL_1(3'b000), .RESET_MODE(OPTION_RESET_MODE), ) _TECHMAP_REPLACE_ ( - .BLKSEL(3'b000), + .BLKSELA(3'b000), + .BLKSELB(3'b000), .CLKA(PORT_A_CLK), .CEA(PORT_A_CLK_EN), .WREA(WREA), .RESETA(RSTA), - .OCEA(1'b0), + .OCEA(1'b1), .ADA(ADA), .DIA(DIA), .DOA(DOA), @@ -250,7 +252,7 @@ if (PORT_A_WIDTH < 9 || PORT_B_WIDTH < 9) begin .CEB(PORT_B_CLK_EN), .WREB(WREB), .RESETB(RSTB), - .OCEB(1'b0), + .OCEB(1'b1), .ADB(ADB), .DIB(DIB), .DOB(DOB), @@ -266,7 +268,7 @@ end else begin assign PORT_A_RD_DATA = DOA; assign PORT_B_RD_DATA = DOB; - DPX9 #( + DPX9B #( `INIT(init_slice_x9) .READ_MODE0(1'b0), .READ_MODE1(1'b0), @@ -274,16 +276,18 @@ end else begin .WRITE_MODE1(PORT_B_OPTION_WRITE_MODE), .BIT_WIDTH_0(PORT_A_WIDTH), .BIT_WIDTH_1(PORT_B_WIDTH), - .BLK_SEL(3'b000), + .BLK_SEL_0(3'b000), + .BLK_SEL_1(3'b000), .RESET_MODE(OPTION_RESET_MODE), ) _TECHMAP_REPLACE_ ( - .BLKSEL(3'b000), + .BLKSELA(3'b000), + .BLKSELB(3'b000), .CLKA(PORT_A_CLK), .CEA(PORT_A_CLK_EN), .WREA(WREA), .RESETA(RSTA), - .OCEA(1'b0), + .OCEA(1'b1), .ADA(ADA), .DIA(DIA), .DOA(DOA), @@ -292,7 +296,7 @@ end else begin .CEB(PORT_B_CLK_EN), .WREB(WREB), .RESETB(RSTB), - .OCEB(1'b0), + .OCEB(1'b1), .ADB(ADB), .DIB(DIB), .DOB(DOB), @@ -344,28 +348,28 @@ if (PORT_W_WIDTH < 9 || PORT_R_WIDTH < 9) begin assign PORT_R_RD_DATA = `x8_rd_data(DO); - SDP #( + SDPB #( `INIT(init_slice_x8) .READ_MODE(1'b0), .BIT_WIDTH_0(`x8_width(PORT_W_WIDTH)), .BIT_WIDTH_1(`x8_width(PORT_R_WIDTH)), - .BLK_SEL(3'b000), + .BLK_SEL_0(3'b000), + .BLK_SEL_1(3'b000), .RESET_MODE(OPTION_RESET_MODE), ) _TECHMAP_REPLACE_ ( - .BLKSEL(3'b000), + .BLKSELA(3'b000), + .BLKSELB(3'b000), .CLKA(PORT_W_CLK), .CEA(PORT_W_CLK_EN), - .WREA(WRE), .RESETA(1'b0), .ADA(ADW), .DI(DI), .CLKB(PORT_R_CLK), .CEB(PORT_R_CLK_EN), - .WREB(1'b0), .RESETB(RST), - .OCE(1'b0), + .OCE(1'b1), .ADB(PORT_R_ADDR), .DO(DO), ); @@ -377,28 +381,28 @@ end else begin assign PORT_R_RD_DATA = DO; - SDPX9 #( + SDPX9B #( `INIT(init_slice_x9) .READ_MODE(1'b0), .BIT_WIDTH_0(PORT_W_WIDTH), .BIT_WIDTH_1(PORT_R_WIDTH), - .BLK_SEL(3'b000), + .BLK_SEL_0(3'b000), + .BLK_SEL_1(3'b000), .RESET_MODE(OPTION_RESET_MODE), ) _TECHMAP_REPLACE_ ( - .BLKSEL(3'b000), + .BLKSELA(3'b000), + .BLKSELB(3'b000), .CLKA(PORT_W_CLK), .CEA(PORT_W_CLK_EN), - .WREA(WRE), .RESETA(1'b0), .ADA(ADW), .DI(DI), .CLKB(PORT_R_CLK), .CEB(PORT_R_CLK_EN), - .WREB(1'b0), .RESETB(RST), - .OCE(1'b0), + .OCE(1'b1), .ADB(PORT_R_ADDR), .DO(DO), ); From 08d7f5472670f02beb498905a4ea3b5e55701e6d Mon Sep 17 00:00:00 2001 From: Catherine Date: Fri, 19 Jan 2024 13:37:48 +0000 Subject: [PATCH 63/70] cxxrtl: move a definition around. NFC --- backends/cxxrtl/runtime/cxxrtl/cxxrtl.h | 256 ++++++++++++------------ 1 file changed, 128 insertions(+), 128 deletions(-) diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index d3b9cf9dbbc..cfdb66f5c22 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -764,134 +764,6 @@ std::ostream &operator<<(std::ostream &os, const value &val) { return os; } -// Must be kept in sync with `struct FmtPart` in kernel/fmt.h! -// Default member initializers would make this a non-aggregate-type in C++11, so they are commented out. -struct fmt_part { - enum { - STRING = 0, - INTEGER = 1, - CHARACTER = 2, - TIME = 3, - } type; - - // STRING type - std::string str; - - // INTEGER/CHARACTER types - // + value val; - - // INTEGER/CHARACTER/TIME types - enum { - RIGHT = 0, - LEFT = 1, - } justify; // = RIGHT; - char padding; // = '\0'; - size_t width; // = 0; - - // INTEGER type - unsigned base; // = 10; - bool signed_; // = false; - bool plus; // = false; - - // TIME type - bool realtime; // = false; - // + int64_t itime; - // + double ftime; - - // Format the part as a string. - // - // The values of `itime` and `ftime` are used for `$time` and `$realtime`, correspondingly. - template - std::string render(value val, int64_t itime, double ftime) - { - // We might want to replace some of these bit() calls with direct - // chunk access if it turns out to be slow enough to matter. - std::string buf; - switch (type) { - case STRING: - return str; - - case CHARACTER: { - buf.reserve(Bits/8); - for (int i = 0; i < Bits; i += 8) { - char ch = 0; - for (int j = 0; j < 8 && i + j < int(Bits); j++) - if (val.bit(i + j)) - ch |= 1 << j; - if (ch != 0) - buf.append({ch}); - } - std::reverse(buf.begin(), buf.end()); - break; - } - - case INTEGER: { - size_t width = Bits; - if (base != 10) { - width = 0; - for (size_t index = 0; index < Bits; index++) - if (val.bit(index)) - width = index + 1; - } - - if (base == 2) { - for (size_t i = width; i > 0; i--) - buf += (val.bit(i - 1) ? '1' : '0'); - } else if (base == 8 || base == 16) { - size_t step = (base == 16) ? 4 : 3; - for (size_t index = 0; index < width; index += step) { - uint8_t value = val.bit(index) | (val.bit(index + 1) << 1) | (val.bit(index + 2) << 2); - if (step == 4) - value |= val.bit(index + 3) << 3; - buf += "0123456789abcdef"[value]; - } - std::reverse(buf.begin(), buf.end()); - } else if (base == 10) { - bool negative = signed_ && val.is_neg(); - if (negative) - val = val.neg(); - if (val.is_zero()) - buf += '0'; - value<(Bits > 4 ? Bits : 4)> xval = val.template zext<(Bits > 4 ? Bits : 4)>(); - while (!xval.is_zero()) { - value<(Bits > 4 ? Bits : 4)> quotient, remainder; - if (Bits >= 4) - std::tie(quotient, remainder) = xval.udivmod(value<(Bits > 4 ? Bits : 4)>{10u}); - else - std::tie(quotient, remainder) = std::make_pair(value<(Bits > 4 ? Bits : 4)>{0u}, xval); - buf += '0' + remainder.template trunc<4>().template get(); - xval = quotient; - } - if (negative || plus) - buf += negative ? '-' : '+'; - std::reverse(buf.begin(), buf.end()); - } else assert(false && "Unsupported base for fmt_part"); - break; - } - - case TIME: { - buf = realtime ? std::to_string(ftime) : std::to_string(itime); - break; - } - } - - std::string str; - assert(width == 0 || padding != '\0'); - if (justify == RIGHT && buf.size() < width) { - size_t pad_width = width - buf.size(); - if (padding == '0' && (buf.front() == '+' || buf.front() == '-')) { - str += buf.front(); - buf.erase(0, 1); - } - str += std::string(pad_width, padding); - } - str += buf; - if (justify == LEFT && buf.size() < width) - str += std::string(width - buf.size(), padding); - return str; - } -}; - template struct wire { static constexpr size_t bits = Bits; @@ -1100,6 +972,134 @@ struct observer { void on_update(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) {} }; +// Must be kept in sync with `struct FmtPart` in kernel/fmt.h! +// Default member initializers would make this a non-aggregate-type in C++11, so they are commented out. +struct fmt_part { + enum { + STRING = 0, + INTEGER = 1, + CHARACTER = 2, + TIME = 3, + } type; + + // STRING type + std::string str; + + // INTEGER/CHARACTER types + // + value val; + + // INTEGER/CHARACTER/TIME types + enum { + RIGHT = 0, + LEFT = 1, + } justify; // = RIGHT; + char padding; // = '\0'; + size_t width; // = 0; + + // INTEGER type + unsigned base; // = 10; + bool signed_; // = false; + bool plus; // = false; + + // TIME type + bool realtime; // = false; + // + int64_t itime; + // + double ftime; + + // Format the part as a string. + // + // The values of `itime` and `ftime` are used for `$time` and `$realtime`, correspondingly. + template + std::string render(value val, int64_t itime, double ftime) + { + // We might want to replace some of these bit() calls with direct + // chunk access if it turns out to be slow enough to matter. + std::string buf; + switch (type) { + case STRING: + return str; + + case CHARACTER: { + buf.reserve(Bits/8); + for (int i = 0; i < Bits; i += 8) { + char ch = 0; + for (int j = 0; j < 8 && i + j < int(Bits); j++) + if (val.bit(i + j)) + ch |= 1 << j; + if (ch != 0) + buf.append({ch}); + } + std::reverse(buf.begin(), buf.end()); + break; + } + + case INTEGER: { + size_t width = Bits; + if (base != 10) { + width = 0; + for (size_t index = 0; index < Bits; index++) + if (val.bit(index)) + width = index + 1; + } + + if (base == 2) { + for (size_t i = width; i > 0; i--) + buf += (val.bit(i - 1) ? '1' : '0'); + } else if (base == 8 || base == 16) { + size_t step = (base == 16) ? 4 : 3; + for (size_t index = 0; index < width; index += step) { + uint8_t value = val.bit(index) | (val.bit(index + 1) << 1) | (val.bit(index + 2) << 2); + if (step == 4) + value |= val.bit(index + 3) << 3; + buf += "0123456789abcdef"[value]; + } + std::reverse(buf.begin(), buf.end()); + } else if (base == 10) { + bool negative = signed_ && val.is_neg(); + if (negative) + val = val.neg(); + if (val.is_zero()) + buf += '0'; + value<(Bits > 4 ? Bits : 4)> xval = val.template zext<(Bits > 4 ? Bits : 4)>(); + while (!xval.is_zero()) { + value<(Bits > 4 ? Bits : 4)> quotient, remainder; + if (Bits >= 4) + std::tie(quotient, remainder) = xval.udivmod(value<(Bits > 4 ? Bits : 4)>{10u}); + else + std::tie(quotient, remainder) = std::make_pair(value<(Bits > 4 ? Bits : 4)>{0u}, xval); + buf += '0' + remainder.template trunc<4>().template get(); + xval = quotient; + } + if (negative || plus) + buf += negative ? '-' : '+'; + std::reverse(buf.begin(), buf.end()); + } else assert(false && "Unsupported base for fmt_part"); + break; + } + + case TIME: { + buf = realtime ? std::to_string(ftime) : std::to_string(itime); + break; + } + } + + std::string str; + assert(width == 0 || padding != '\0'); + if (justify == RIGHT && buf.size() < width) { + size_t pad_width = width - buf.size(); + if (padding == '0' && (buf.front() == '+' || buf.front() == '-')) { + str += buf.front(); + buf.erase(0, 1); + } + str += std::string(pad_width, padding); + } + str += buf; + if (justify == LEFT && buf.size() < width) + str += std::string(width - buf.size(), padding); + return str; + } +}; + // Tag class to disambiguate values/wires and their aliases. struct debug_alias {}; From b74d33d1b8329ecda69ff6889479a097d8835664 Mon Sep 17 00:00:00 2001 From: Catherine Date: Fri, 19 Jan 2024 13:33:14 +0000 Subject: [PATCH 64/70] fmt: rename TIME to VLOG_TIME. The behavior of these format specifiers is highly specific to Verilog (`$time` and `$realtime` are only defined relative to `$timescale`) and may not fit other languages well, if at all. If they choose to use it, it is now clear what they are opting into. This commit also simplifies the CXXRTL code generation for these format specifiers. --- backends/cxxrtl/cxxrtl_backend.cc | 8 ++++---- backends/cxxrtl/runtime/cxxrtl/cxxrtl.h | 26 ++++++++++++++----------- kernel/fmt.cc | 26 ++++++++++++------------- kernel/fmt.h | 8 ++++---- tests/fmt/always_full_tb.cc | 2 +- 5 files changed, 37 insertions(+), 33 deletions(-) diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index ef718067f25..620b530b007 100644 --- a/backends/cxxrtl/cxxrtl_backend.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -1072,9 +1072,9 @@ struct CxxrtlWorker { dump_sigspec_rhs(cell->getPort(ID::EN)); f << " == value<1>{1u}) {\n"; inc_indent(); - f << indent << "auto formatter = [&](int64_t itime, double ftime) {\n"; + f << indent << "auto formatter = [&](struct performer *performer) {\n"; inc_indent(); - fmt.emit_cxxrtl(f, indent, [this](const RTLIL::SigSpec &sig) { dump_sigspec_rhs(sig); }); + fmt.emit_cxxrtl(f, indent, [this](const RTLIL::SigSpec &sig) { dump_sigspec_rhs(sig); }, "performer"); dec_indent(); f << indent << "};\n"; f << indent << "if (performer) {\n"; @@ -1082,11 +1082,11 @@ struct CxxrtlWorker { f << indent << "static const metadata_map attributes = "; dump_metadata_map(cell->attributes); f << ";\n"; - f << indent << "performer->on_print(formatter(performer->time(), performer->realtime()), attributes);\n"; + f << indent << "performer->on_print(formatter(performer), attributes);\n"; dec_indent(); f << indent << "} else {\n"; inc_indent(); - f << indent << print_output << " << formatter(0, 0.0);\n"; + f << indent << print_output << " << formatter(performer);\n"; dec_indent(); f << indent << "}\n"; dec_indent(); diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index cfdb66f5c22..a8a011d15aa 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -948,10 +948,10 @@ typedef std::map metadata_map; // An object that can be passed to a `eval()` method in order to act on side effects. struct performer { // Called to evaluate a Verilog `$time` expression. - virtual int64_t time() const { return 0; } + virtual int64_t vlog_time() const { return 0; } // Called to evaluate a Verilog `$realtime` expression. - virtual double realtime() const { return time(); } + virtual double vlog_realtime() const { return vlog_time(); } // Called when a `$print` cell is triggered. virtual void on_print(const std::string &output, const metadata_map &attributes) { std::cout << output; } @@ -976,10 +976,10 @@ struct observer { // Default member initializers would make this a non-aggregate-type in C++11, so they are commented out. struct fmt_part { enum { - STRING = 0, - INTEGER = 1, + STRING = 0, + INTEGER = 1, CHARACTER = 2, - TIME = 3, + VLOG_TIME = 3, } type; // STRING type @@ -988,7 +988,7 @@ struct fmt_part { // INTEGER/CHARACTER types // + value val; - // INTEGER/CHARACTER/TIME types + // INTEGER/CHARACTER/VLOG_TIME types enum { RIGHT = 0, LEFT = 1, @@ -1001,16 +1001,16 @@ struct fmt_part { bool signed_; // = false; bool plus; // = false; - // TIME type + // VLOG_TIME type bool realtime; // = false; // + int64_t itime; // + double ftime; // Format the part as a string. // - // The values of `itime` and `ftime` are used for `$time` and `$realtime`, correspondingly. + // The values of `vlog_time` and `vlog_realtime` are used for Verilog `$time` and `$realtime`, correspondingly. template - std::string render(value val, int64_t itime, double ftime) + std::string render(value val, performer *performer = nullptr) { // We might want to replace some of these bit() calls with direct // chunk access if it turns out to be slow enough to matter. @@ -1077,8 +1077,12 @@ struct fmt_part { break; } - case TIME: { - buf = realtime ? std::to_string(ftime) : std::to_string(itime); + case VLOG_TIME: { + if (performer) { + buf = realtime ? std::to_string(performer->vlog_realtime()) : std::to_string(performer->vlog_time()); + } else { + buf = realtime ? std::to_string(0.0) : std::to_string(0); + } break; } } diff --git a/kernel/fmt.cc b/kernel/fmt.cc index 7d16b20c1d5..18eb7cb7193 100644 --- a/kernel/fmt.cc +++ b/kernel/fmt.cc @@ -110,9 +110,9 @@ void Fmt::parse_rtlil(const RTLIL::Cell *cell) { } else if (fmt[i] == 'c') { part.type = FmtPart::CHARACTER; } else if (fmt[i] == 't') { - part.type = FmtPart::TIME; + part.type = FmtPart::VLOG_TIME; } else if (fmt[i] == 'r') { - part.type = FmtPart::TIME; + part.type = FmtPart::VLOG_TIME; part.realtime = true; } else { log_assert(false && "Unexpected character in format substitution"); @@ -172,7 +172,7 @@ void Fmt::emit_rtlil(RTLIL::Cell *cell) const { } break; - case FmtPart::TIME: + case FmtPart::VLOG_TIME: log_assert(part.sig.size() == 0); YS_FALLTHROUGH case FmtPart::CHARACTER: @@ -205,7 +205,7 @@ void Fmt::emit_rtlil(RTLIL::Cell *cell) const { fmt += part.signed_ ? 's' : 'u'; } else if (part.type == FmtPart::CHARACTER) { fmt += 'c'; - } else if (part.type == FmtPart::TIME) { + } else if (part.type == FmtPart::VLOG_TIME) { if (part.realtime) fmt += 'r'; else @@ -328,7 +328,7 @@ void Fmt::parse_verilog(const std::vector &args, bool sformat_lik case VerilogFmtArg::TIME: { FmtPart part = {}; - part.type = FmtPart::TIME; + part.type = FmtPart::VLOG_TIME; part.realtime = arg->realtime; part.padding = ' '; part.width = 20; @@ -419,7 +419,7 @@ void Fmt::parse_verilog(const std::vector &args, bool sformat_lik part.padding = ' '; } else if (fmt[i] == 't' || fmt[i] == 'T') { if (arg->type == VerilogFmtArg::TIME) { - part.type = FmtPart::TIME; + part.type = FmtPart::VLOG_TIME; part.realtime = arg->realtime; if (!has_width && !has_leading_zero) part.width = 20; @@ -541,7 +541,7 @@ std::vector Fmt::emit_verilog() const break; } - case FmtPart::TIME: { + case FmtPart::VLOG_TIME: { VerilogFmtArg arg; arg.type = VerilogFmtArg::TIME; if (part.realtime) @@ -592,7 +592,7 @@ std::string escape_cxx_string(const std::string &input) return output; } -void Fmt::emit_cxxrtl(std::ostream &os, std::string indent, std::function emit_sig) const +void Fmt::emit_cxxrtl(std::ostream &os, std::string indent, std::function emit_sig, const std::string &context) const { os << indent << "std::string buf;\n"; for (auto &part : parts) { @@ -602,7 +602,7 @@ void Fmt::emit_cxxrtl(std::ostream &os, std::string indent, std::function &args, bool sformat_like, int default_base, RTLIL::IdString task_name, RTLIL::IdString module_name); std::vector emit_verilog() const; - void emit_cxxrtl(std::ostream &os, std::string indent, std::function emit_sig) const; + void emit_cxxrtl(std::ostream &os, std::string indent, std::function emit_sig, const std::string &context) const; std::string render() const; diff --git a/tests/fmt/always_full_tb.cc b/tests/fmt/always_full_tb.cc index a29a8ae05bc..1dd80d0a230 100644 --- a/tests/fmt/always_full_tb.cc +++ b/tests/fmt/always_full_tb.cc @@ -3,7 +3,7 @@ int main() { struct : public performer { - int64_t time() const override { return 1; } + int64_t vlog_time() const override { return 1; } void on_print(const std::string &output, const cxxrtl::metadata_map &) override { std::cerr << output; } } performer; From fc5ff7a265293099e25812afbbbe8e687f0b62e8 Mon Sep 17 00:00:00 2001 From: Catherine Date: Fri, 19 Jan 2024 15:54:33 +0000 Subject: [PATCH 65/70] cxxrtl: always lazily format print messages. This is mostly useful for collecting coverage for the future `$check` cell, where, depending on the flavor, formatting a message may not be wanted even for a failed assertion. --- backends/cxxrtl/cxxrtl_backend.cc | 32 +++++++++++++++++++++---- backends/cxxrtl/runtime/cxxrtl/cxxrtl.h | 15 +++++++++--- tests/fmt/always_full_tb.cc | 2 +- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index 620b530b007..c9c644a52e1 100644 --- a/backends/cxxrtl/cxxrtl_backend.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -1072,21 +1072,43 @@ struct CxxrtlWorker { dump_sigspec_rhs(cell->getPort(ID::EN)); f << " == value<1>{1u}) {\n"; inc_indent(); - f << indent << "auto formatter = [&](struct performer *performer) {\n"; + dict fmt_args; + f << indent << "struct : public lazy_fmt {\n"; inc_indent(); - fmt.emit_cxxrtl(f, indent, [this](const RTLIL::SigSpec &sig) { dump_sigspec_rhs(sig); }, "performer"); + f << indent << "std::string operator() () const override {\n"; + inc_indent(); + fmt.emit_cxxrtl(f, indent, [&](const RTLIL::SigSpec &sig) { + if (sig.size() == 0) + f << "value<0>()"; + else { + std::string arg_name = "arg" + std::to_string(fmt_args.size()); + fmt_args[arg_name] = sig; + f << arg_name; + } + }, "performer"); + dec_indent(); + f << indent << "}\n"; + f << indent << "struct performer *performer;\n"; + for (auto arg : fmt_args) + f << indent << "value<" << arg.second.size() << "> " << arg.first << ";\n"; dec_indent(); - f << indent << "};\n"; + f << indent << "} formatter;\n"; + f << indent << "formatter.performer = performer;\n"; + for (auto arg : fmt_args) { + f << indent << "formatter." << arg.first << " = "; + dump_sigspec_rhs(arg.second); + f << ";\n"; + } f << indent << "if (performer) {\n"; inc_indent(); f << indent << "static const metadata_map attributes = "; dump_metadata_map(cell->attributes); f << ";\n"; - f << indent << "performer->on_print(formatter(performer), attributes);\n"; + f << indent << "performer->on_print(formatter, attributes);\n"; dec_indent(); f << indent << "} else {\n"; inc_indent(); - f << indent << print_output << " << formatter(performer);\n"; + f << indent << print_output << " << formatter();\n"; dec_indent(); f << indent << "}\n"; dec_indent(); diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index a8a011d15aa..55057149721 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -945,16 +945,25 @@ struct metadata { typedef std::map metadata_map; +struct performer; + +// An object that allows formatting a string lazily. +struct lazy_fmt { + virtual std::string operator() () const = 0; +}; + // An object that can be passed to a `eval()` method in order to act on side effects. struct performer { - // Called to evaluate a Verilog `$time` expression. + // Called by generated formatting code to evaluate a Verilog `$time` expression. virtual int64_t vlog_time() const { return 0; } - // Called to evaluate a Verilog `$realtime` expression. + // Called by generated formatting code to evaluate a Verilog `$realtime` expression. virtual double vlog_realtime() const { return vlog_time(); } // Called when a `$print` cell is triggered. - virtual void on_print(const std::string &output, const metadata_map &attributes) { std::cout << output; } + virtual void on_print(const lazy_fmt &formatter, const metadata_map &attributes) { + std::cout << formatter(); + } }; // An object that can be passed to a `commit()` method in order to produce a replay log of every state change in diff --git a/tests/fmt/always_full_tb.cc b/tests/fmt/always_full_tb.cc index 1dd80d0a230..94991ca25b5 100644 --- a/tests/fmt/always_full_tb.cc +++ b/tests/fmt/always_full_tb.cc @@ -4,7 +4,7 @@ int main() { struct : public performer { int64_t vlog_time() const override { return 1; } - void on_print(const std::string &output, const cxxrtl::metadata_map &) override { std::cerr << output; } + void on_print(const lazy_fmt &output, const cxxrtl::metadata_map &) override { std::cerr << output(); } } performer; cxxrtl_design::p_always__full uut; From 8649e306682e44b7f3e1b869d1043041db00b13e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 20 Jan 2024 00:16:07 +0000 Subject: [PATCH 66/70] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f2af053be44..88dfb43740c 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LDLIBS += -lrt endif endif -YOSYS_VER := 0.37+15 +YOSYS_VER := 0.37+21 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo From b3e7390c0eb89544c0da6d9bc3b381f6ce450228 Mon Sep 17 00:00:00 2001 From: Stephen Tong <14918218+stong@users.noreply.github.com> Date: Sun, 21 Jan 2024 16:32:05 -0500 Subject: [PATCH 67/70] Fix typo in stat help --- passes/cmds/stat.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/passes/cmds/stat.cc b/passes/cmds/stat.cc index f0021cf8769..d34373c1c0e 100644 --- a/passes/cmds/stat.cc +++ b/passes/cmds/stat.cc @@ -366,7 +366,7 @@ struct StatPass : public Pass { log(" use cell area information from the provided liberty file\n"); log("\n"); log(" -tech \n"); - log(" print area estemate for the specified technology. Currently supported\n"); + log(" print area estimate for the specified technology. Currently supported\n"); log(" values for : xilinx, cmos\n"); log("\n"); log(" -width\n"); From 3d9e44d18227c136cea031c667a5158fe31ac996 Mon Sep 17 00:00:00 2001 From: Catherine Date: Fri, 19 Jan 2024 14:23:08 +0000 Subject: [PATCH 68/70] hierarchy: keep display statements, like formal assertions. --- passes/hierarchy/hierarchy.cc | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/passes/hierarchy/hierarchy.cc b/passes/hierarchy/hierarchy.cc index bf013750365..69cc6fb914d 100644 --- a/passes/hierarchy/hierarchy.cc +++ b/passes/hierarchy/hierarchy.cc @@ -655,6 +655,17 @@ void hierarchy_clean(RTLIL::Design *design, RTLIL::Module *top, bool purge_lib) log("Removed %d unused modules.\n", del_counter); } +bool set_keep_print(std::map &cache, RTLIL::Module *mod) +{ + if (cache.count(mod) == 0) + for (auto c : mod->cells()) { + RTLIL::Module *m = mod->design->module(c->type); + if ((m != nullptr && set_keep_print(cache, m)) || c->type == ID($print)) + return cache[mod] = true; + } + return cache[mod]; +} + bool set_keep_assert(std::map &cache, RTLIL::Module *mod) { if (cache.count(mod) == 0) @@ -762,6 +773,11 @@ struct HierarchyPass : public Pass { log(" -nodefaults\n"); log(" do not resolve input port default values\n"); log("\n"); + log(" -nokeep_prints\n"); + log(" per default this pass sets the \"keep\" attribute on all modules\n"); + log(" that directly or indirectly display text on the terminal.\n"); + log(" This option disables this behavior.\n"); + log("\n"); log(" -nokeep_asserts\n"); log(" per default this pass sets the \"keep\" attribute on all modules\n"); log(" that directly or indirectly contain one or more formal properties.\n"); @@ -818,6 +834,7 @@ struct HierarchyPass : public Pass { bool keep_positionals = false; bool keep_portwidths = false; bool nodefaults = false; + bool nokeep_prints = false; bool nokeep_asserts = false; std::vector generate_cells; std::vector generate_ports; @@ -893,6 +910,10 @@ struct HierarchyPass : public Pass { nodefaults = true; continue; } + if (args[argidx] == "-nokeep_prints") { + nokeep_prints = true; + continue; + } if (args[argidx] == "-nokeep_asserts") { nokeep_asserts = true; continue; @@ -1091,6 +1112,15 @@ struct HierarchyPass : public Pass { } } + if (!nokeep_prints) { + std::map cache; + for (auto mod : design->modules()) + if (set_keep_print(cache, mod)) { + log("Module %s directly or indirectly displays text -> setting \"keep\" attribute.\n", log_id(mod)); + mod->set_bool_attribute(ID::keep); + } + } + if (!nokeep_asserts) { std::map cache; for (auto mod : design->modules()) From cfcd0b57299a9f2b13453e396c7a7b72d0aee16e Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Mon, 22 Jan 2024 17:18:39 +0100 Subject: [PATCH 69/70] Checkout specific iverilog version (can be master as well) --- .github/workflows/test-linux.yml | 5 ++++- .github/workflows/test-macos.yml | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-linux.yml b/.github/workflows/test-linux.yml index a16481726d3..103c060a2c0 100644 --- a/.github/workflows/test-linux.yml +++ b/.github/workflows/test-linux.yml @@ -85,13 +85,16 @@ jobs: shell: bash run: | git clone https://github.com/steveicarus/iverilog.git + cd iverilog + git checkout ${{ vars.IVERILOG_VERSION }} + echo "IVERILOG_GIT=$(git rev-parse HEAD)" >> $GITHUB_ENV - name: Cache iverilog id: cache-iverilog uses: actions/cache@v3 with: path: .local/ - key: ${{ matrix.os.id }}-${{ hashFiles('iverilog/.git/refs/heads/master') }} + key: ${{ matrix.os.id }}-${{ env.IVERILOG_GIT }} - name: Build iverilog if: steps.cache-iverilog.outputs.cache-hit != 'true' diff --git a/.github/workflows/test-macos.yml b/.github/workflows/test-macos.yml index 2b48b725237..62fbc59e82f 100644 --- a/.github/workflows/test-macos.yml +++ b/.github/workflows/test-macos.yml @@ -41,13 +41,16 @@ jobs: shell: bash run: | git clone https://github.com/steveicarus/iverilog.git + cd iverilog + git checkout ${{ vars.IVERILOG_VERSION }} + echo "IVERILOG_GIT=$(git rev-parse HEAD)" >> $GITHUB_ENV - name: Cache iverilog id: cache-iverilog uses: actions/cache@v3 with: path: .local/ - key: ${{ matrix.os.id }}-${{ hashFiles('iverilog/.git/refs/heads/master') }} + key: ${{ matrix.os.id }}-${{ env.IVERILOG_GIT }} - name: Build iverilog if: steps.cache-iverilog.outputs.cache-hit != 'true' From 2f9fcc2e501b5f0a449d315738c4a8d06cf38434 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 23 Jan 2024 00:16:43 +0000 Subject: [PATCH 70/70] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 88dfb43740c..83642584724 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LDLIBS += -lrt endif endif -YOSYS_VER := 0.37+21 +YOSYS_VER := 0.37+27 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo