From d130f7fca2872ac07401f650c3fe23e79b655286 Mon Sep 17 00:00:00 2001 From: Charlotte Date: Sat, 12 Aug 2023 11:59:39 +1000 Subject: [PATCH 01/76] 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 0a011932d..f8bd79782 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 fe7e3a329..dd675e917 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 ede757273..9895a97e4 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 34adb7af3..3f84d65a2 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 145b9c33b..1347b7044 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 ff7372916..8436ebda0 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 d4d641a9c..5c30db644 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 3f7f134e4..8523a4676 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 1e564d1b2..c79456f70 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 ff79ec534..438629c3e 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 7451a1b3e..7e9936804 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 5d9c84dac..5c18d30e2 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 9a73f9b0c..bd72572d5 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 a14a78e72..02ce2fef9 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 d005cd40c..b2a2bb445 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 bcf2b964a..ca7339ff0 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 5d923db56..a14b79509 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 c22ab6928..5131ed646 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 44ce7e674..e9698386e 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 429a79e3c..358423f32 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 d6ba0de43..37fc91d0e 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 914a72347..6c5becd5f 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 fbdcbf048..dc60c69c4 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 878c80b3f..99be37f5e 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 61f19b09b..19ce0667d 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 f8964f146..a10559cac 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 e43ddd093..db0ec54ee 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 abe88a6cb..5f230a03e 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 c65066a9c..4f1da7ce7 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 2007cd6e4..74589dfeb 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 e0008a259..e80cd4214 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 44ce7e674..e9698386e 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 e1a36c694..833e8c3f4 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 eeb309347..624043750 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 44ce7e674..e9698386e 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 1bcd8e423..a7b5fc4a0 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 47bcfd6da..b9e79f34a 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 4a5bf01a3..b75e54dd3 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 a261f4ea6..a0a6eba0a 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 19b650007..c318b648d 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 491f65148..a2206eea7 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 9a01447f7..5266fffe5 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 2cc09f801..7765907c7 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 618204aed..77b4a2918 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 3c0689619..29f7c2680 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 482d44d9a..af0fe1b98 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 61528c2b4..c76e036a3 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 67cfe44e8..87fac67fe 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 db4b7ca82..84b7c4ac4 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/76] 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 7621341a7..bea8876cc 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/76] 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 bea8876cc..33b34fdbf 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/76] 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 33b34fdbf..2c9d82914 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/76] 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 302ba76e5..85022c1cf 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 3ea6bca23ec62d0869bc7c48fa6cc6ec4c3c0109 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 13 Dec 2023 00:16:10 +0000 Subject: [PATCH 06/76] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0a92a5856..8f3e473ea 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LDLIBS += -lrt endif endif -YOSYS_VER := 0.36+13 +YOSYS_VER := 0.36+30 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo From dbff694e3d6b48907d496e8b2517ad794243467a Mon Sep 17 00:00:00 2001 From: Henri Nurmi Date: Sat, 9 Dec 2023 22:27:05 +0200 Subject: [PATCH 07/76] cxxrtl: Use the base name of the interface file for the include directive Prior to this fix, the `CxxrtlBackend` used the entire path for the include directive when a separated interface file is generated (via the `-header` option). This commit updates the code to use the base name of the interface file. Since the C++11 standard is used by default, we cannot take advantage of the `std::filesystem` to get the basename. --- backends/cxxrtl/cxxrtl_backend.cc | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index a322ed308..ef276d9a5 100644 --- a/backends/cxxrtl/cxxrtl_backend.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -628,6 +628,22 @@ std::string escape_cxx_string(const std::string &input) return output; } +std::string basename(const std::string &filepath) +{ +#ifndef _WIN32 + const std::string dir_seps = "/"; +#else + const std::string dir_seps = "\\/"; +#endif + size_t sep_pos = filepath.find_last_of(dir_seps); + if (sep_pos != std::string::npos && sep_pos + 1 < filepath.length()) { + return filepath.substr(sep_pos + 1); + } + else { + return filepath; + } +} + template std::string get_hdl_name(T *object) { @@ -2571,7 +2587,7 @@ struct CxxrtlWorker { } if (split_intf) - f << "#include \"" << intf_filename << "\"\n"; + f << "#include \"" << basename(intf_filename) << "\"\n"; else f << "#include \n"; if (has_prints) From 79c0bfcb2283d1a7665290a9a2074d0205c77fdc Mon Sep 17 00:00:00 2001 From: Henri Nurmi Date: Sun, 10 Dec 2023 07:42:27 +0200 Subject: [PATCH 08/76] cxxrtl: Remove unnecessary length check --- backends/cxxrtl/cxxrtl_backend.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index ef276d9a5..ab4456bba 100644 --- a/backends/cxxrtl/cxxrtl_backend.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -636,7 +636,7 @@ std::string basename(const std::string &filepath) const std::string dir_seps = "\\/"; #endif size_t sep_pos = filepath.find_last_of(dir_seps); - if (sep_pos != std::string::npos && sep_pos + 1 < filepath.length()) { + if (sep_pos != std::string::npos) { return filepath.substr(sep_pos + 1); } else { From 1c8e58a7362e4dc5f0780ecfda43d81e4866c973 Mon Sep 17 00:00:00 2001 From: Henri Nurmi Date: Sun, 10 Dec 2023 08:15:41 +0200 Subject: [PATCH 09/76] cxxrtl: Fix formating --- backends/cxxrtl/cxxrtl_backend.cc | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index ab4456bba..1ab865a27 100644 --- a/backends/cxxrtl/cxxrtl_backend.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -630,18 +630,16 @@ std::string escape_cxx_string(const std::string &input) std::string basename(const std::string &filepath) { -#ifndef _WIN32 - const std::string dir_seps = "/"; -#else +#ifdef _WIN32 const std::string dir_seps = "\\/"; +#else + const std::string dir_seps = "/"; #endif size_t sep_pos = filepath.find_last_of(dir_seps); - if (sep_pos != std::string::npos) { + if (sep_pos != std::string::npos) return filepath.substr(sep_pos + 1); - } - else { + else return filepath; - } } template From ff53f3d2b6e267358cf4c3d08574416c79441ef9 Mon Sep 17 00:00:00 2001 From: Merry Date: Wed, 13 Dec 2023 12:02:30 +0000 Subject: [PATCH 10/76] cxxrtl: Fix value::shl --- Makefile | 1 + backends/cxxrtl/runtime/cxxrtl/cxxrtl.h | 1 + tests/cxxrtl/.gitignore | 1 + tests/cxxrtl/run-test.sh | 12 ++++++++++++ tests/cxxrtl/test_value.cc | 15 +++++++++++++++ 5 files changed, 30 insertions(+) create mode 100644 tests/cxxrtl/.gitignore create mode 100755 tests/cxxrtl/run-test.sh create mode 100644 tests/cxxrtl/test_value.cc diff --git a/Makefile b/Makefile index 8f3e473ea..8a72e3c70 100644 --- a/Makefile +++ b/Makefile @@ -888,6 +888,7 @@ endif +cd tests/verilog && bash run-test.sh +cd tests/xprop && bash run-test.sh $(SEEDOPT) +cd tests/fmt && bash run-test.sh + +cd tests/cxxrtl && bash run-test.sh @echo "" @echo " Passed \"make test\"." @echo "" diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index 2d5451287..635c867ae 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -419,6 +419,7 @@ struct value : public expr_base> { carry = (shift_bits == 0) ? 0 : data[n] >> (chunk::bits - shift_bits); } + result.data[result.chunks - 1] &= result.msb_mask; return result; } diff --git a/tests/cxxrtl/.gitignore b/tests/cxxrtl/.gitignore new file mode 100644 index 000000000..91caee986 --- /dev/null +++ b/tests/cxxrtl/.gitignore @@ -0,0 +1 @@ +cxxrtl-test-* diff --git a/tests/cxxrtl/run-test.sh b/tests/cxxrtl/run-test.sh new file mode 100755 index 000000000..473a5a349 --- /dev/null +++ b/tests/cxxrtl/run-test.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -ex + +run_subtest () { + local subtest=$1; shift + + ${CC:-gcc} -std=c++11 -o cxxrtl-test-${subtest} -I../../backends/cxxrtl/runtime test_${subtest}.cc -lstdc++ + ./cxxrtl-test-${subtest} +} + +run_subtest value diff --git a/tests/cxxrtl/test_value.cc b/tests/cxxrtl/test_value.cc new file mode 100644 index 000000000..ca05b89ab --- /dev/null +++ b/tests/cxxrtl/test_value.cc @@ -0,0 +1,15 @@ +#include +#include + +#include "cxxrtl/cxxrtl.h" + +int main() +{ + { + // shl exceeding Bits should be masked + cxxrtl::value<6> a(1u); + cxxrtl::value<6> b(8u); + cxxrtl::value<6> c = a.shl(b); + assert(c.get() == 0); + } +} \ No newline at end of file From ded63bedd589255e6e06747edcdbf262444ac379 Mon Sep 17 00:00:00 2001 From: Merry Date: Wed, 13 Dec 2023 12:11:57 +0000 Subject: [PATCH 11/76] cxxrtl: Fix value::sshr --- backends/cxxrtl/runtime/cxxrtl/cxxrtl.h | 11 ++++++----- tests/cxxrtl/test_value.cc | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index 635c867ae..c9ddb815f 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -430,12 +430,12 @@ struct value : public expr_base> { // Detect shifts definitely large than Bits early. for (size_t n = 1; n < amount.chunks; n++) if (amount.data[n] != 0) - return {}; + return (Signed && is_neg()) ? value().bit_not() : value(); // Past this point we can use the least significant chunk as the shift size. size_t shift_chunks = amount.data[0] / chunk::bits; size_t shift_bits = amount.data[0] % chunk::bits; if (shift_chunks >= chunks) - return {}; + return (Signed && is_neg()) ? value().bit_not() : value(); value result; chunk::type carry = 0; for (size_t n = 0; n < chunks - shift_chunks; n++) { @@ -444,12 +444,13 @@ struct value : public expr_base> { : data[chunks - 1 - n] << (chunk::bits - shift_bits); } if (Signed && is_neg()) { - size_t top_chunk_idx = (Bits - shift_bits) / chunk::bits; - size_t top_chunk_bits = (Bits - shift_bits) % chunk::bits; + size_t top_chunk_idx = amount.data[0] > Bits ? 0 : (Bits - amount.data[0]) / chunk::bits; + size_t top_chunk_bits = amount.data[0] > Bits ? 0 : (Bits - amount.data[0]) % chunk::bits; for (size_t n = top_chunk_idx + 1; n < chunks; n++) result.data[n] = chunk::mask; - if (shift_bits != 0) + if (amount.data[0] != 0) result.data[top_chunk_idx] |= chunk::mask << top_chunk_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 ca05b89ab..c553f6112 100644 --- a/tests/cxxrtl/test_value.cc +++ b/tests/cxxrtl/test_value.cc @@ -12,4 +12,28 @@ int main() cxxrtl::value<6> c = a.shl(b); assert(c.get() == 0); } + + { + // sshr of unreasonably large size should sign extend correctly + cxxrtl::value<64> a(0u, 0x80000000u); + cxxrtl::value<64> b(0u, 1u); + cxxrtl::value<64> c = a.sshr(b); + assert(c.get() == 0xffffffffffffffffu); + } + + { + // sshr of exteeding Bits should sign extend correctly + cxxrtl::value<8> a(0x80u); + cxxrtl::value<8> b(10u); + cxxrtl::value<8> c = a.sshr(b); + assert(c.get() == 0xffu); + } + + { + // Sign extension should occur correctly + cxxrtl::value<64> a(0x23456789u, 0x8abcdef1u); + cxxrtl::value<8> b(32u); + cxxrtl::value<64> c = a.sshr(b); + assert(c.get() == 0xffffffff8abcdef1u); + } } \ No newline at end of file From d7cb6981b55a15eee3d0d9be84446826a65c0403 Mon Sep 17 00:00:00 2001 From: Merry Date: Wed, 13 Dec 2023 12:15:12 +0000 Subject: [PATCH 12/76] cxxrtl: Fix value::ctlz --- backends/cxxrtl/runtime/cxxrtl/cxxrtl.h | 3 ++- tests/cxxrtl/test_value.cc | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index c9ddb815f..d861b7e07 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -511,7 +511,8 @@ struct value : public expr_base> { for (size_t n = 0; n < chunks; n++) { chunk::type x = data[chunks - 1 - n]; // First add to `count` as if the chunk is zero - count += (n == 0 ? Bits % chunk::bits : chunk::bits); + constexpr size_t msb_chunk_bits = Bits % chunk::bits != 0 ? Bits % chunk::bits : chunk::bits; + count += (n == 0 ? msb_chunk_bits : chunk::bits); // If the chunk isn't zero, correct the `count` value and return if (x != 0) { for (; x != 0; count--) diff --git a/tests/cxxrtl/test_value.cc b/tests/cxxrtl/test_value.cc index c553f6112..4d68372bb 100644 --- a/tests/cxxrtl/test_value.cc +++ b/tests/cxxrtl/test_value.cc @@ -36,4 +36,10 @@ int main() cxxrtl::value<64> c = a.sshr(b); assert(c.get() == 0xffffffff8abcdef1u); } -} \ No newline at end of file + + { + // ctlz should work with Bits that are a multiple of chunk size + cxxrtl::value<32> a(0x00040000u); + assert(a.ctlz() == 13); + } +} From 29e0cc6acd4eb1c288cf0742b4bfa978fab3c1da Mon Sep 17 00:00:00 2001 From: Merry Date: Wed, 13 Dec 2023 12:18:13 +0000 Subject: [PATCH 13/76] cxxrtl: Add simple fuzzing tests for value --- tests/cxxrtl/run-test.sh | 1 + tests/cxxrtl/test_value_fuzz.cc | 251 ++++++++++++++++++++++++++++++++ 2 files changed, 252 insertions(+) create mode 100644 tests/cxxrtl/test_value_fuzz.cc diff --git a/tests/cxxrtl/run-test.sh b/tests/cxxrtl/run-test.sh index 473a5a349..ff2c35faf 100755 --- a/tests/cxxrtl/run-test.sh +++ b/tests/cxxrtl/run-test.sh @@ -10,3 +10,4 @@ run_subtest () { } run_subtest value +run_subtest value_fuzz diff --git a/tests/cxxrtl/test_value_fuzz.cc b/tests/cxxrtl/test_value_fuzz.cc new file mode 100644 index 000000000..4428e9f6e --- /dev/null +++ b/tests/cxxrtl/test_value_fuzz.cc @@ -0,0 +1,251 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "cxxrtl/cxxrtl.h" + +template +T rand_int(T min = std::numeric_limits::min(), T max = std::numeric_limits::max()) +{ + static_assert(std::is_integral::value, "T must be an integral type."); + static_assert(!std::is_same::value && !std::is_same::value, + "Using char with uniform_int_distribution is undefined behavior."); + + static std::mt19937 generator = [] { + std::random_device rd; + std::mt19937 mt{rd()}; + return mt; + }(); + + std::uniform_int_distribution dist(min, max); + return dist(generator); +} + +struct BinaryOperationBase +{ + void tweak_input(uint64_t &a, uint64_t &b) {} +}; + +template +void test_binary_operation_for_bitsize(Operation &op) +{ + constexpr int iteration_count = 10000000; + + constexpr uint64_t mask = std::numeric_limits::max() >> (64 - Bits); + + using chunk_type = typename cxxrtl::value::chunk::type; + constexpr size_t chunk_bits = cxxrtl::value::chunk::bits; + + for (int iteration = 0; iteration < iteration_count; iteration++) { + uint64_t ia = rand_int() >> (64 - Bits); + uint64_t ib = rand_int() >> (64 - Bits); + op.tweak_input(ia, ib); + + cxxrtl::value va, vb; + for (size_t i = 0; i * chunk_bits < Bits; i++) { + va.data[i] = (chunk_type)(ia >> (i * chunk_bits)); + vb.data[i] = (chunk_type)(ib >> (i * chunk_bits)); + } + + uint64_t iresult = op.reference_impl(Bits, ia, ib) & mask; + cxxrtl::value vresult = op.template testing_impl(va, vb); + + for (size_t i = 0; i * chunk_bits < Bits; i++) { + if ((chunk_type)(iresult >> (i * chunk_bits)) != vresult.data[i]) { + std::printf("Test failure:\n"); + std::printf("Bits: %i\n", Bits); + std::printf("a: %016lx\n", ia); + std::printf("b: %016lx\n", ib); + std::printf("iresult: %016lx\n", iresult); + std::printf("vresult: %016lx\n", vresult.template get()); + + std::terminate(); + } + } + } + std::printf("Test passed @ Bits = %i.\n", Bits); +} + +template +void test_binary_operation(Operation &op) +{ + // Test at a variety of bitwidths + test_binary_operation_for_bitsize<8>(op); + test_binary_operation_for_bitsize<32>(op); + test_binary_operation_for_bitsize<42>(op); + test_binary_operation_for_bitsize<63>(op); + test_binary_operation_for_bitsize<64>(op); +} + +template +struct UnaryOperationWrapper : BinaryOperationBase +{ + Operation &op; + + UnaryOperationWrapper(Operation &op) : op(op) {} + + uint64_t reference_impl(size_t bits, uint64_t a, uint64_t b) + { + return op.reference_impl(bits, a); + } + + template + cxxrtl::value testing_impl(cxxrtl::value a, cxxrtl::value b) + { + return op.template testing_impl(a); + } +}; + +template +void test_unary_operation(Operation &op) +{ + UnaryOperationWrapper wrapped(op); + test_binary_operation(wrapped); +} + +struct ShlTest : BinaryOperationBase +{ + ShlTest() + { + std::printf("Randomized tests for value::shl:\n"); + test_binary_operation(*this); + } + + uint64_t reference_impl(size_t bits, uint64_t a, uint64_t b) + { + return b >= 64 ? 0 : a << b; + } + + template + cxxrtl::value testing_impl(cxxrtl::value a, cxxrtl::value b) + { + return a.shl(b); + } + + void tweak_input(uint64_t &, uint64_t &b) + { + b &= 0x7f; + } +} shl; + +struct ShrTest : BinaryOperationBase +{ + ShrTest() + { + std::printf("Randomized tests for value::shr:\n"); + test_binary_operation(*this); + } + + uint64_t reference_impl(size_t bits, uint64_t a, uint64_t b) + { + return b >= 64 ? 0 : a >> b; + } + + template + cxxrtl::value testing_impl(cxxrtl::value a, cxxrtl::value b) + { + return a.shr(b); + } + + void tweak_input(uint64_t &, uint64_t &b) + { + b &= 0x7f; + } +} shr; + +struct SshrTest : BinaryOperationBase +{ + SshrTest() + { + std::printf("Randomized tests for value::sshr:\n"); + test_binary_operation(*this); + } + + uint64_t reference_impl(size_t bits, uint64_t a, uint64_t b) + { + int64_t sa = (int64_t)(a << (64 - bits)); + return sa >> (b >= bits ? 63 : (b + 64 - bits)); + } + + template + cxxrtl::value testing_impl(cxxrtl::value a, cxxrtl::value b) + { + return a.sshr(b); + } + + void tweak_input(uint64_t &, uint64_t &b) + { + b &= 0x7f; + } +} sshr; + +struct AddTest : BinaryOperationBase +{ + AddTest() + { + std::printf("Randomized tests for value::add:\n"); + test_binary_operation(*this); + } + + uint64_t reference_impl(size_t bits, uint64_t a, uint64_t b) + { + return a + b; + } + + template + cxxrtl::value testing_impl(cxxrtl::value a, cxxrtl::value b) + { + return a.add(b); + } +} add; + +struct SubTest : BinaryOperationBase +{ + SubTest() + { + std::printf("Randomized tests for value::sub:\n"); + test_binary_operation(*this); + } + + uint64_t reference_impl(size_t bits, uint64_t a, uint64_t b) + { + return a - b; + } + + template + cxxrtl::value testing_impl(cxxrtl::value a, cxxrtl::value b) + { + return a.sub(b); + } +} sub; + +struct CtlzTest +{ + CtlzTest() + { + std::printf("Randomized tests for value::ctlz:\n"); + test_unary_operation(*this); + } + + uint64_t reference_impl(size_t bits, uint64_t a) + { + if (a == 0) + return bits; + return __builtin_clzl(a) - (64 - bits); + } + + template + cxxrtl::value testing_impl(cxxrtl::value a) + { + size_t result = a.ctlz(); + return cxxrtl::value((cxxrtl::chunk_t)result); + } +} ctlz; + +int main() +{ +} From 1dff3c83d97724ee7d4872404b8df7201773734c Mon Sep 17 00:00:00 2001 From: Merry Date: Wed, 13 Dec 2023 12:27:06 +0000 Subject: [PATCH 14/76] tests/cxxrtl: Add -O2 --- tests/cxxrtl/run-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cxxrtl/run-test.sh b/tests/cxxrtl/run-test.sh index ff2c35faf..89de71c6b 100755 --- a/tests/cxxrtl/run-test.sh +++ b/tests/cxxrtl/run-test.sh @@ -5,7 +5,7 @@ set -ex run_subtest () { local subtest=$1; shift - ${CC:-gcc} -std=c++11 -o cxxrtl-test-${subtest} -I../../backends/cxxrtl/runtime test_${subtest}.cc -lstdc++ + ${CC:-gcc} -std=c++11 -O2 -o cxxrtl-test-${subtest} -I../../backends/cxxrtl/runtime test_${subtest}.cc -lstdc++ ./cxxrtl-test-${subtest} } From 39fdde87a782695a98bc3e93cb767086ee23c5cc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 14 Dec 2023 00:16:03 +0000 Subject: [PATCH 15/76] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8a72e3c70..446682e86 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LDLIBS += -lrt endif endif -YOSYS_VER := 0.36+30 +YOSYS_VER := 0.36+40 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo 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 16/76] 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 d861b7e07..78dbf3707 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 4d68372bb..0750d412e 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 17/76] 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 dd3f9ac48..34657be26 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 18/76] 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 2be4fb679..1a2a45703 100644 --- a/backends/smt2/smtbmc_incremental.py +++ b/backends/smt2/smtbmc_incremental.py @@ -194,9 +194,31 @@ class Incremental: 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 @@ class Incremental: "or": expr_andor, "=": expr_eq, "yw": expr_yw, + "smtlib": expr_smtlib, "!": expr_label, } @@ -251,7 +274,8 @@ class Incremental: ) ): 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 @@ class Incremental: 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 @@ class Incremental: 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 @@ class Incremental: "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 19/76] 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 a39500c2d..b7e25851c 100644 --- a/backends/smt2/witness.py +++ b/backends/smt2/witness.py @@ -183,7 +183,8 @@ This requires a Yosys witness AIGER map file as generated by 'write_aiger -ywmap @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 20/76] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 446682e86..e367846e2 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 21/76] 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 6783e5b29..b57250fc7 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 22/76] 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 3afbeda07..f254c46aa 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 23/76] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e367846e2..00569d95c 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 24/76] 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 47aba71a8..25aa94b80 100644 --- a/kernel/hashlib.h +++ b/kernel/hashlib.h @@ -420,7 +420,7 @@ public: operator const_iterator() const { return const_iterator(ptr, index); } }; - dict() + constexpr dict() { } @@ -855,7 +855,7 @@ public: operator const_iterator() const { return const_iterator(ptr, index); } }; - pool() + constexpr pool() { } @@ -1062,6 +1062,10 @@ public: 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 25/76] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 00569d95c..60d67d05a 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 26/76] 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 1098bc8f6..bed1ba572 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 523c86e73..ecf163dbf 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 153f3995f..9df979c33 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 27/76] 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 2a500b56b..dfa1ed6af 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 5ac9a4664..cd7b7953e 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 28/76] 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 000000000..51f59321e --- /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 29/76] 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 1ab865a27..2c35a1943 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 78dbf3707..53fb3dbc0 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 30/76] 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 53fb3dbc0..183fbb2c7 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 31/76] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 60d67d05a..3da89690f 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 32/76] 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 735672a43..fd70695d3 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 33/76] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3da89690f..946c323e7 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 34/76] 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 048457234..2b48b7252 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 35/76] 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 183fbb2c7..3f8247226 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 36/76] 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 51f59321e..f37c2b656 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 @@ public: 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 @@ public: 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 @@ public: } 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 @@ public: 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 37/76] 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 000000000..94f59bb0d --- /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 38/76] 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 fd70695d3..9ff2c5c86 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 39/76] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 946c323e7..c6d117fc5 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 40/76] 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 dfa1ed6af..98a922ff4 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 ae291374d..56fe3ef3b 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 41/76] 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 2dc061e89..9e303b9db 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 000000000..75415c313 --- /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 42/76] 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 77b4a2918..f36c74bd2 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 56fe3ef3b..6eb952165 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 43/76] 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 34e624993..4b2b7a822 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 f789e930b..97903d0a0 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 98a922ff4..945f286a1 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 44/76] 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 945f286a1..51ed95621 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); - AstNode *shamt = ref_sel; + // All operations are carried out in a new block. + newNode = new AstNode(AST_BLOCK); - // 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); + // 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)); + + // Calculate lsb from position. + AstNode *shift_val = pos->clone(); + + // 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 45/76] 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 000000000..5f65ac284 --- /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 46/76] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c6d117fc5..8bde98a1c 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 47/76] 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 8c4fadb69..b07534184 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 48/76] 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 97903d0a0..31679e4df 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 51ed95621..d45b53db5 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 49/76] 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 4b2b7a822..fe075b270 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 31679e4df..c44746131 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 d45b53db5..5370c4187 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 9b277c6b9..5c59fe3af 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 50/76] 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 9ff2c5c86..71b9c5fd8 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 51/76] 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 71b9c5fd8..1fa31e31e 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 494c0651c..0f0d79123 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 0bae0f673..0a502162e 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 52/76] 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 2c35a1943..ac2f756d6 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 53/76] 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 ac2f756d6..ff11d7fe0 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 54/76] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8bde98a1c..0a9392696 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 55/76] 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 b07534184..542eb5c18 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 56/76] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0a9392696..274a93b98 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 57/76] 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 2a500b56b..aa5093ec7 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 ec181a142..2e683b8eb 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 4c30a3632..4870e2cac 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 58/76] 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 3b079d964..3907285f3 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 a9982649b..4c691c7a5 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 59/76] 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 d57cbc6bb..8c2695dbe 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 60/76] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 274a93b98..f6c28adeb 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 61/76] Release version 0.37 --- CHANGELOG | 15 +++++++++++++-- Makefile | 4 ++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4fb60904a..0570554cd 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 f6c28adeb..e0e850b11 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 62/76] Next dev cycle --- CHANGELOG | 3 +++ Makefile | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 0570554cd..990f9e17d 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 e0e850b11..5428a5daf 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 63/76] =?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 3f8247226..e2729bfe4 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 94f59bb0d..e44b1c4e1 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h @@ -561,12 +561,12 @@ public: 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 64/76] 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 ff11d7fe0..30995ea11 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 e2729bfe4..bd336f481 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 e44b1c4e1..d824fdb83 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h @@ -556,22 +556,20 @@ public: 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 65/76] 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 | 220 ++++++++++++++---------- kernel/fmt.cc | 124 ++++++------- kernel/fmt.h | 5 +- tests/fmt/always_full_tb.cc | 7 +- 5 files changed, 192 insertions(+), 173 deletions(-) diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index 30995ea11..e4a000627 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 bd336f481..654f13b4a 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,102 +763,134 @@ 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; +// 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; - 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; + // 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 -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; - } - - 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]; - } - 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; - } - 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); - } - os << std::string(pad_width, vf.padding); - } - 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. struct observer { diff --git a/kernel/fmt.cc b/kernel/fmt.cc index 383fa7de1..7d16b20c1 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) { - for (auto &part : parts) { - 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(); + 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: 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 39a278c56..126d020c0 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 @@ public: 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 229f78aeb..a29a8ae05 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 66/76] 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 e4a000627..3b0c2827d 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 654f13b4a..7d6a75d4a 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 67/76] 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 3b0c2827d..c6a4265fc 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 7d6a75d4a..d15772a3c 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 68/76] 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 c6a4265fc..ef718067f 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 d15772a3c..d3b9cf9db 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 69/76] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5428a5daf..a3447e005 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 70/76] 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 9737fde89..adabd2700 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 000000000..bfdbeb748 --- /dev/null +++ b/tests/verific/clocking.ys @@ -0,0 +1,10 @@ +read -sv < Date: Fri, 19 Jan 2024 00:16:38 +0000 Subject: [PATCH 71/76] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a3447e005..f2af053be 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 72/76] 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 7ffc91bac..5e1299c28 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 73/76] 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 d3b9cf9db..cfdb66f5c 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 74/76] 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 ef718067f..620b530b0 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 cfdb66f5c..a8a011d15 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 7d16b20c1..18eb7cb71 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 a29a8ae05..1dd80d0a2 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 75/76] 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 620b530b0..c9c644a52 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 a8a011d15..550571497 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 1dd80d0a2..94991ca25 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 76/76] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f2af053be..88dfb4374 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