Compare commits

..

4 commits

Author SHA1 Message Date
921cddbe06
disable tests/reg_alloc.rs for now
All checks were successful
/ deps (push) Successful in 17s
/ test (push) Successful in 51m28s
2025-03-18 17:26:47 -07:00
b9d68c0019
WIP: splitting reg_alloc
Some checks failed
/ deps (push) Successful in 14s
/ test (push) Failing after 2m7s
2025-03-14 18:26:34 -07:00
9fa959652e
update fayalite 2025-03-10 19:50:55 -07:00
9199fdf35c
add name_mangling_serde crate 2025-03-08 22:38:30 -08:00
79 changed files with 35963 additions and 1819884 deletions

View file

@ -0,0 +1,77 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# See Notices.txt for copyright information
on:
workflow_call:
outputs:
cache-primary-key:
value: ${{ jobs.deps.outputs.cache-primary-key }}
jobs:
deps:
runs-on: debian-12
outputs:
cache-primary-key: ${{ steps.restore-deps.outputs.cache-primary-key }}
steps:
- uses: https://git.libre-chip.org/mirrors/checkout@v3
with:
fetch-depth: 0
- uses: https://git.libre-chip.org/mirrors/cache/restore@v3
id: restore-deps
with:
path: deps
key: ${{ github.repository }}-deps-${{ runner.os }}-${{ hashFiles('.forgejo/workflows/deps.yml') }}
lookup-only: true
- name: Install Apt packages
if: steps.restore-deps.outputs.cache-hit != 'true'
run: |
apt-get update -qq
apt-get install -qq \
bison \
build-essential \
ccache \
clang \
cvc5 \
flex \
gawk \
g++ \
git \
libboost-filesystem-dev \
libboost-python-dev \
libboost-system-dev \
libffi-dev \
libreadline-dev \
lld \
pkg-config \
python3 \
python3-click \
tcl-dev \
zlib1g-dev
- name: Install Firtool
if: steps.restore-deps.outputs.cache-hit != 'true'
run: |
mkdir -p deps
wget -O deps/firrtl.tar.gz https://github.com/llvm/circt/releases/download/firtool-1.86.0/firrtl-bin-linux-x64.tar.gz
sha256sum -c - <<<'bf6f4ab18ae76f135c944efbd81e25391c31c1bd0617c58ab0592640abefee14 deps/firrtl.tar.gz'
tar -C deps -xvaf deps/firrtl.tar.gz
rm -rf deps/firtool
mv deps/firtool-1.86.0 deps/firtool
- name: Get SymbiYosys
if: steps.restore-deps.outputs.cache-hit != 'true'
run: |
git clone --depth=1 --branch=yosys-0.45 https://git.libre-chip.org/mirrors/sby deps/sby
- name: Build Z3
if: steps.restore-deps.outputs.cache-hit != 'true'
run: |
git clone --depth=1 --recursive --branch=z3-4.13.3 https://git.libre-chip.org/mirrors/z3 deps/z3
(cd deps/z3; PYTHON=python3 ./configure --prefix=/usr/local)
make -C deps/z3/build -j"$(nproc)"
- name: Build Yosys
if: steps.restore-deps.outputs.cache-hit != 'true'
run: |
git clone --depth=1 --recursive --branch=0.45 https://git.libre-chip.org/mirrors/yosys deps/yosys
make -C deps/yosys -j"$(nproc)"
- uses: https://git.libre-chip.org/mirrors/cache/save@v3
if: steps.restore-deps.outputs.cache-hit != 'true'
with:
path: deps
key: ${{ steps.restore-deps.outputs.cache-primary-key }}

View file

@ -3,18 +3,56 @@
on: [push, pull_request]
jobs:
deps:
uses: ./.forgejo/workflows/deps.yml
test:
runs-on: debian-12
container:
image: git.libre-chip.org/libre-chip/fayalite-deps:latest
needs: deps
steps:
- uses: actions/checkout@v3
- uses: https://git.libre-chip.org/mirrors/checkout@v3
with:
fetch-depth: 0
- run: |
scripts/check-copyright.sh
- run: |
apt-get update -qq
apt-get install -qq \
bison \
build-essential \
ccache \
clang \
cvc5 \
flex \
gawk \
git \
libboost-filesystem-dev \
libboost-python-dev \
libboost-system-dev \
libffi-dev \
libreadline-dev \
lld \
pkg-config \
python3 \
python3-click \
tcl-dev \
z3 \
zlib1g-dev
- run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.82.0
source "$HOME/.cargo/env"
echo "$PATH" >> "$GITHUB_PATH"
- uses: https://git.libre-chip.org/mirrors/cache/restore@v3
with:
path: deps
key: ${{ needs.deps.outputs.cache-primary-key }}
fail-on-cache-miss: true
- run: |
make -C deps/z3/build install
make -C deps/sby install
make -C deps/yosys install
export PATH="$(realpath deps/firtool/bin):$PATH"
echo "$PATH" >> "$GITHUB_PATH"
- uses: https://git.libre-chip.org/mirrors/rust-cache@v2
with:
save-if: ${{ github.ref == 'refs/heads/master' }}
- run: rustup override set 1.93.0
- run: cargo test

3
.gitattributes vendored
View file

@ -1,3 +0,0 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# See Notices.txt for copyright information
*.vcd linguist-generated=true

1
.gitignore vendored
View file

@ -1,4 +1,3 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# See Notices.txt for copyright information
/target
OPF_PowerISA_v3.1C.pdf

611
Cargo.lock generated
View file

@ -1,20 +1,17 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
version = 3
[[package]]
name = "adler2"
version = "2.0.1"
name = "ahash"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"memchr",
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
@ -96,36 +93,6 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
[[package]]
name = "base16ct"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bindgen"
version = "0.71.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"itertools",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn",
]
[[package]]
name = "bitflags"
version = "2.6.0"
@ -168,48 +135,21 @@ dependencies = [
"generic-array",
]
[[package]]
name = "bytes"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
[[package]]
name = "cc"
version = "1.2.51"
version = "1.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203"
checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clang-sys"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "clap"
version = "4.5.20"
@ -232,15 +172,6 @@ dependencies = [
"strsim",
]
[[package]]
name = "clap_complete"
version = "4.5.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2348487adcd4631696ced64ccdb40d38ac4d31cae7f2eec8817fcea1b9d1c43c"
dependencies = [
"clap",
]
[[package]]
name = "clap_derive"
version = "4.5.18"
@ -275,17 +206,9 @@ checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
name = "cpu"
version = "0.1.0"
dependencies = [
"base16ct 1.0.0",
"fayalite",
"hex-literal",
"parse_powerisa_pdf",
"regex",
"roxmltree",
"name_mangling_serde",
"serde",
"sha2",
"simple-mermaid",
"ureq",
"which",
]
[[package]]
@ -297,15 +220,6 @@ dependencies = [
"libc",
]
[[package]]
name = "crc32fast"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
dependencies = [
"cfg-if",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
@ -361,12 +275,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.14"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
dependencies = [
"libc",
"windows-sys 0.61.2",
"windows-sys 0.52.0",
]
[[package]]
@ -388,27 +302,23 @@ checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
[[package]]
name = "fayalite"
version = "0.3.0"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#cf3e6cfc6bc33eebf2d2862c7a1948b9cf40ecac"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#d453755bb2cd0b6f2340f3e49058d29a2ee279e8"
dependencies = [
"base64",
"bitvec",
"blake3",
"clap",
"clap_complete",
"ctor",
"eyre",
"fayalite-proc-macros",
"fayalite-visit-gen",
"hashbrown 0.15.5",
"hashbrown",
"jobslot",
"num-bigint",
"num-traits",
"once_cell",
"ordered-float",
"os_pipe",
"petgraph",
"serde",
"serde_json",
"sha2",
"tempfile",
"vec_map",
"which",
@ -417,7 +327,7 @@ dependencies = [
[[package]]
name = "fayalite-proc-macros"
version = "0.3.0"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#cf3e6cfc6bc33eebf2d2862c7a1948b9cf40ecac"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#d453755bb2cd0b6f2340f3e49058d29a2ee279e8"
dependencies = [
"fayalite-proc-macros-impl",
]
@ -425,9 +335,9 @@ dependencies = [
[[package]]
name = "fayalite-proc-macros-impl"
version = "0.3.0"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#cf3e6cfc6bc33eebf2d2862c7a1948b9cf40ecac"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#d453755bb2cd0b6f2340f3e49058d29a2ee279e8"
dependencies = [
"base16ct 0.2.0",
"base16ct",
"num-bigint",
"prettyplease",
"proc-macro2",
@ -440,7 +350,7 @@ dependencies = [
[[package]]
name = "fayalite-visit-gen"
version = "0.3.0"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#cf3e6cfc6bc33eebf2d2862c7a1948b9cf40ecac"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#d453755bb2cd0b6f2340f3e49058d29a2ee279e8"
dependencies = [
"indexmap",
"prettyplease",
@ -452,34 +362,12 @@ dependencies = [
"thiserror",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff"
[[package]]
name = "fixedbitset"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
[[package]]
name = "flate2"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "funty"
version = "2.0.0"
@ -498,62 +386,31 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.2.16"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "getrandom"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasip2",
]
[[package]]
name = "glob"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "hashbrown"
version = "0.15.5"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"ahash",
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hex-literal"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1"
[[package]]
name = "home"
version = "0.5.9"
@ -563,22 +420,6 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "http"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
dependencies = [
"bytes",
"itoa",
]
[[package]]
name = "httparse"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
name = "indenter"
version = "0.3.3"
@ -587,14 +428,13 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
[[package]]
name = "indexmap"
version = "2.13.0"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5"
dependencies = [
"equivalent",
"hashbrown 0.16.1",
"hashbrown",
"serde",
"serde_core",
]
[[package]]
@ -603,15 +443,6 @@ version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.11"
@ -620,39 +451,23 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "jobslot"
version = "0.2.23"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58715c67c327da7f1558708348d68c207fd54900c4ae0529e29305d04d795b8c"
checksum = "fe10868679d7a24c2c67d862d0e64a342ce9aef7cdde9ce8019bd35d353d458d"
dependencies = [
"cfg-if",
"derive_destructure2",
"getrandom 0.3.4",
"getrandom",
"libc",
"scopeguard",
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
name = "libc"
version = "0.2.180"
version = "0.2.159"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
[[package]]
name = "libloading"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
dependencies = [
"cfg-if",
"windows-link",
]
[[package]]
name = "libm"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
[[package]]
name = "linux-raw-sys"
@ -660,12 +475,6 @@ version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "memchr"
version = "2.7.4"
@ -673,42 +482,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
name = "name_mangling_serde"
version = "0.1.0"
dependencies = [
"adler2",
"simd-adler32",
]
[[package]]
name = "mupdf-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13e9a0d4e844ab50315d43312f3d62f72c77205b07c8ee21cbd4b52bdc2a9910"
dependencies = [
"bindgen",
"cc",
"pkg-config",
"regex",
"zerocopy",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
"serde",
"serde_json",
]
[[package]]
@ -741,56 +519,29 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.21.3"
version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "ordered-float"
version = "5.1.0"
name = "os_pipe"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d"
checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982"
dependencies = [
"num-traits",
"rand",
"serde",
"libc",
"windows-sys 0.59.0",
]
[[package]]
name = "parse_powerisa_pdf"
version = "0.1.0"
source = "git+https://git.libre-chip.org/libre-chip/parse_powerisa_pdf.git?branch=master#38a1fb328bd44f26389c28fbf66716154f4113dc"
dependencies = [
"indexmap",
"libm",
"mupdf-sys",
"quick-xml",
]
[[package]]
name = "percent-encoding"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "petgraph"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
version = "0.6.5"
source = "git+https://github.com/programmerjake/petgraph.git?rev=258ea8071209a924b73fe96f9f87a3b7b45cbc9f#258ea8071209a924b73fe96f9f87a3b7b45cbc9f"
dependencies = [
"fixedbitset",
"hashbrown 0.15.5",
"indexmap",
"serde",
]
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "prettyplease"
version = "0.2.22"
@ -810,15 +561,6 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "quick-xml"
version = "0.38.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c"
dependencies = [
"memchr",
]
[[package]]
name = "quote"
version = "1.0.37"
@ -828,95 +570,12 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "radium"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"rand_core",
"serde",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"serde",
]
[[package]]
name = "regex"
version = "1.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.16",
"libc",
"untrusted",
"windows-sys 0.52.0",
]
[[package]]
name = "roxmltree"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb"
dependencies = [
"memchr",
]
[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustix"
version = "0.38.37"
@ -930,41 +589,6 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "rustls"
version = "0.23.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b"
dependencies = [
"log",
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-pki-types"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282"
dependencies = [
"zeroize",
]
[[package]]
name = "rustls-webpki"
version = "0.103.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "ryu"
version = "1.0.18"
@ -979,28 +603,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.228"
version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
dependencies = [
"proc-macro2",
"quote",
@ -1022,9 +636,9 @@ dependencies = [
[[package]]
name = "sha2"
version = "0.10.9"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
@ -1037,30 +651,12 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "simd-adler32"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
[[package]]
name = "simple-mermaid"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589144a964b4b30fe3a83b4bb1a09e2475aac194ec832a046a23e75bddf9eb29"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.96"
@ -1123,47 +719,6 @@ version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "ureq"
version = "3.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d39cb1dbab692d82a977c0392ffac19e188bd9186a9f32806f0aaa859d75585a"
dependencies = [
"base64",
"flate2",
"log",
"percent-encoding",
"rustls",
"rustls-pki-types",
"ureq-proto",
"utf-8",
"webpki-roots",
]
[[package]]
name = "ureq-proto"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f"
dependencies = [
"base64",
"http",
"httparse",
"log",
]
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "utf8parse"
version = "0.2.2"
@ -1184,27 +739,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasip2"
version = "1.0.1+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "webpki-roots"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c"
dependencies = [
"rustls-pki-types",
]
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "which"
@ -1214,17 +751,10 @@ checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f"
dependencies = [
"either",
"home",
"regex",
"rustix",
"winsafe",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.52.0"
@ -1243,15 +773,6 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
@ -1322,12 +843,6 @@ version = "0.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
[[package]]
name = "wit-bindgen"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "wyz"
version = "0.5.1"
@ -1339,26 +854,20 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.27"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.27"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"

View file

@ -7,23 +7,17 @@ members = ["crates/*"]
[workspace.package]
version = "0.1.0"
license = "LGPL-3.0-or-later"
edition = "2024"
edition = "2021"
repository = ""
keywords = []
categories = []
rust-version = "1.93.0"
rust-version = "1.82.0"
[workspace.dependencies]
base16ct = "1.0.0"
name_mangling_serde = { version = "=0.1.0", path = "crates/name_mangling_serde" }
fayalite = { git = "https://git.libre-chip.org/libre-chip/fayalite.git", version = "0.3.0", branch = "master" }
hex-literal = "1.1.0"
parse_powerisa_pdf = { git = "https://git.libre-chip.org/libre-chip/parse_powerisa_pdf.git", version = "0.1.0", branch = "master" }
roxmltree = "0.21.1"
serde = { version = "1.0.202", features = ["derive"] }
sha2 = "0.10.9"
simple-mermaid = "0.2.0"
ureq = "3.1.4"
which = { version = "6.0.3", features = ["regex"] }
serde_json = { version = "1.0.117", features = ["preserve_order"] }
[profile.dev]
opt-level = 1

View file

@ -1,15 +0,0 @@
<!--
SPDX-License-Identifier: LGPL-3.0-or-later
See Notices.txt for copyright information
-->
# Libre-Chip's CPU
<https://libre-chip.org/first_arch/index.html>
# Funding
## NLnet Grants
* [Libre-Chip CPU with proof of No Spectre bugs](https://nlnet.nl/project/Libre-Chip-proof/) 2024-12-324 [(progress)](https://git.libre-chip.org/libre-chip/grant-tracking/src/branch/master/nlnet-2024-12-324/progress.md)
This project was funded through the [NGI0 Commons Fund](https://nlnet.nl/commonsfund), a fund established by [NLnet](https://nlnet.nl/) with financial support from the European Commission's [Next Generation Internet](https://ngi.eu) programme, under the aegis of [DG Communications Networks, Content and Technology](https://commission.europa.eu/about-european-commission/departments-and-executive-agencies/communications-networks-content-and-technology_en) under grant agreement &numero; [101135429](https://cordis.europa.eu/project/id/101135429). Additional funding is made available by the [Swiss State Secretariat for Education, Research and Innovation](https://www.sbfi.admin.ch/sbfi/en/home.html) (SERI).

View file

@ -16,23 +16,8 @@ version.workspace = true
[dependencies]
fayalite.workspace = true
roxmltree.workspace = true
serde.workspace = true
simple-mermaid.workspace = true
[build-dependencies]
base16ct.workspace = true
hex-literal.workspace = true
parse_powerisa_pdf.workspace = true
sha2.workspace = true
ureq.workspace = true
[dev-dependencies]
base16ct.workspace = true
hex-literal.workspace = true
regex = "1.12.2"
sha2.workspace = true
which.workspace = true
name_mangling_serde.workspace = true
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(todo)'] }

View file

@ -1 +0,0 @@
../../README.md

View file

@ -1,96 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use parse_powerisa_pdf::parse_powerisa_pdf_and_generate_xml;
use sha2::{Digest, Sha256};
use std::{
error::Error,
io::{ErrorKind, Read},
path::Path,
};
const EXPECTED_FILE_SIZE: u64 = 6425593;
const EXPECTED_FILE_HASH: &[u8; 32] =
&hex_literal::hex!("56372d23ece7e9e2c1b381a639443982a3e16e38109df1c141d655b779b61fdb");
fn verify_powerisa_file(file: impl std::io::Read) -> Result<Vec<u8>, Box<dyn Error>> {
let mut buf = Vec::with_capacity(usize::try_from(EXPECTED_FILE_SIZE)? + 1);
file.take(EXPECTED_FILE_SIZE + 1).read_to_end(&mut buf)?;
if buf.len() > EXPECTED_FILE_SIZE as usize {
Err(format!("file is bigger than the expected length {EXPECTED_FILE_SIZE}").into())
} else if buf.len() < EXPECTED_FILE_SIZE as usize {
Err(format!(
"file is smaller than the expected length {EXPECTED_FILE_SIZE}: actual length {}",
buf.len()
)
.into())
} else {
let hash = Sha256::digest(&buf);
let hash = &*hash;
if hash != EXPECTED_FILE_HASH {
Err(format!(
"file's SHA256 hash doesn't match the expected hash: expected: {:x} got: {:x}",
base16ct::HexDisplay(EXPECTED_FILE_HASH),
base16ct::HexDisplay(hash)
)
.into())
} else {
Ok(buf)
}
}
}
fn is_powerisa_pdf(path: impl AsRef<Path>) -> Result<bool, Box<dyn Error>> {
let path = path.as_ref();
let metadata = match std::fs::metadata(path) {
Err(e) if e.kind() == ErrorKind::NotFound => return Ok(false),
v => v?,
};
if !metadata.is_file() {
return Ok(false);
}
let file = std::fs::File::open(path)?;
verify_powerisa_file(file)?;
Ok(true)
}
fn out_dir() -> String {
std::env::var("OUT_DIR").expect("OUT_DIR env var is not set or invalid")
}
fn get_powerisa_pdf_name_and_dir<'a>(
out_dir: &'a str,
) -> Result<(&'static str, &'a Path), Box<dyn Error>> {
const FILE_NAME: &str = "OPF_PowerISA_v3.1C.pdf";
let out_dir = Path::new(out_dir);
if is_powerisa_pdf(FILE_NAME)? {
println!("cargo::rerun-if-changed={FILE_NAME}");
return Ok((FILE_NAME, Path::new(".")));
}
let full_path = out_dir.join(FILE_NAME);
let full_path = full_path
.into_os_string()
.into_string()
.expect("should be valid UTF-8");
if is_powerisa_pdf(&full_path)? {
println!("cargo::rerun-if-changed={full_path}");
return Ok((FILE_NAME, out_dir));
}
const URL: &str = "https://libre-chip.org/OPF_PowerISA_v3.1C.pdf";
println!("cargo::warning={FILE_NAME} not found locally, downloading from {URL}");
let buf = verify_powerisa_file(ureq::get(URL).call()?.into_body().into_reader())?;
std::fs::write(&full_path, buf)?;
println!("cargo::rerun-if-changed={full_path}");
Ok((FILE_NAME, out_dir))
}
fn main() -> Result<(), Box<dyn Error>> {
let out_dir = out_dir();
let (pdf_name, pdf_dir) = get_powerisa_pdf_name_and_dir(&out_dir)?;
let old_dir = std::env::current_dir()?;
std::env::set_current_dir(pdf_dir)?;
let xml = parse_powerisa_pdf_and_generate_xml(pdf_name, None, false)?;
std::env::set_current_dir(old_dir)?;
std::fs::write(Path::new(&out_dir).join("powerisa-instructions.xml"), xml)?;
Ok(())
}

View file

@ -1,11 +1,17 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{instruction::CONST_ZERO_UNIT_NUM, unit::UnitKind};
use fayalite::{expr::HdlPartialOrdImpl, prelude::*};
use crate::{
instruction::{PRegNum, CONST_ZERO_UNIT_NUM},
unit::UnitKind,
};
use fayalite::{
intern::{Intern, Interned},
prelude::*,
};
use serde::{Deserialize, Serialize};
use std::num::NonZeroUsize;
#[derive(Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct UnitConfig {
pub kind: UnitKind,
@ -22,21 +28,14 @@ impl UnitConfig {
}
}
#[derive(Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct CpuConfig {
pub units: Vec<UnitConfig>,
pub units: Interned<[UnitConfig]>,
pub out_reg_num_width: usize,
pub fetch_width: NonZeroUsize,
pub max_branches_per_fetch: NonZeroUsize,
pub max_fetches_in_flight: NonZeroUsize,
pub log2_fetch_width_in_bytes: u8,
pub log2_cache_line_size_in_bytes: u8,
pub log2_l1_i_cache_line_count: u8,
pub l1_i_cache_max_misses_in_flight: NonZeroUsize,
/// default value for [`UnitConfig::max_in_flight`]
pub default_unit_max_in_flight: NonZeroUsize,
pub rob_size: NonZeroUsize,
}
impl CpuConfig {
@ -47,46 +46,18 @@ impl CpuConfig {
};
v
};
pub const DEFAULT_MAX_BRANCHES_PER_FETCH: NonZeroUsize = {
let Some(v) = NonZeroUsize::new(1) else {
unreachable!();
};
v
};
pub const DEFAULT_MAX_FETCHES_IN_FLIGHT: NonZeroUsize = {
let Some(v) = NonZeroUsize::new(16) else {
unreachable!();
};
v
};
pub const DEFAULT_LOG2_FETCH_WIDTH_IN_BYTES: u8 = 3;
pub const DEFAULT_LOG2_CACHE_LINE_SIZE_IN_BYTES: u8 = 6;
pub const DEFAULT_LOG2_L1_I_CACHE_LINE_COUNT: u8 = 8;
pub const DEFAULT_L1_I_CACHE_MAX_MISSES_IN_FLIGHT: NonZeroUsize = {
let Some(v) = NonZeroUsize::new(2) else {
unreachable!();
};
v
};
pub const DEFAULT_UNIT_MAX_IN_FLIGHT: NonZeroUsize = {
let Some(v) = NonZeroUsize::new(8) else {
unreachable!();
};
v
};
pub fn new(units: Vec<UnitConfig>, rob_size: NonZeroUsize) -> Self {
pub fn new(units: Interned<[UnitConfig]>) -> Self {
Self {
units,
out_reg_num_width: Self::DEFAULT_OUT_REG_NUM_WIDTH,
fetch_width: Self::DEFAULT_FETCH_WIDTH,
max_branches_per_fetch: Self::DEFAULT_MAX_BRANCHES_PER_FETCH,
max_fetches_in_flight: Self::DEFAULT_MAX_FETCHES_IN_FLIGHT,
log2_fetch_width_in_bytes: Self::DEFAULT_LOG2_FETCH_WIDTH_IN_BYTES,
log2_cache_line_size_in_bytes: Self::DEFAULT_LOG2_CACHE_LINE_SIZE_IN_BYTES,
log2_l1_i_cache_line_count: Self::DEFAULT_LOG2_L1_I_CACHE_LINE_COUNT,
l1_i_cache_max_misses_in_flight: Self::DEFAULT_L1_I_CACHE_MAX_MISSES_IN_FLIGHT,
default_unit_max_in_flight: Self::DEFAULT_UNIT_MAX_IN_FLIGHT,
rob_size,
}
}
pub fn non_const_unit_nums(&self) -> std::ops::Range<usize> {
@ -95,135 +66,83 @@ impl CpuConfig {
pub fn unit_num_width(&self) -> usize {
UInt::range(CONST_ZERO_UNIT_NUM..self.non_const_unit_nums().end).width()
}
pub fn p_reg_num_width(&self) -> usize {
self.unit_num_width() + self.out_reg_num_width
}
pub fn unit_max_in_flight(&self, unit_index: usize) -> NonZeroUsize {
self.units[unit_index]
.max_in_flight
.unwrap_or(self.default_unit_max_in_flight)
}
/// the maximum of all [`unit_max_in_flight()`][Self::unit_max_in_flight()]
pub fn max_unit_max_in_flight(&self) -> NonZeroUsize {
(0..self.units.len())
.map(|unit_index| self.unit_max_in_flight(unit_index))
.max()
.unwrap_or(self.default_unit_max_in_flight)
}
pub fn fetch_width_in_bytes(&self) -> usize {
1usize
.checked_shl(self.log2_fetch_width_in_bytes.into())
.expect("log2_fetch_width_in_bytes is too big")
}
pub fn cache_line_size_in_bytes(&self) -> usize {
1usize
.checked_shl(self.log2_cache_line_size_in_bytes.into())
.expect("log2_cache_line_size_in_bytes is too big")
}
pub fn log2_fetches_per_cache_line(&self) -> usize {
self.log2_cache_line_size_in_bytes
.checked_sub(self.log2_fetch_width_in_bytes)
.expect("cache line size in bytes must not be smaller than fetch width in bytes")
.into()
}
pub fn fetches_per_cache_line(&self) -> usize {
self.log2_fetches_per_cache_line()
.try_into()
.ok()
.and_then(|v| 1usize.checked_shl(v))
.expect("log2_fetches_per_cache_line is too big")
}
pub fn l1_i_cache_line_count(&self) -> usize {
1usize
.checked_shl(self.log2_l1_i_cache_line_count.into())
.expect("log2_l1_i_cache_line_count is too big")
}
pub fn log2_l1_i_cache_size_in_bytes(&self) -> usize {
self.log2_l1_i_cache_line_count as usize + self.log2_cache_line_size_in_bytes as usize
}
pub fn l1_i_cache_size_in_bytes(&self) -> usize {
1usize
.checked_shl(self.log2_l1_i_cache_size_in_bytes() as _)
.expect("L1 I-Cache is too big")
pub fn retire_queue_index_width(&self) -> usize {
let max_in_flight: usize = (0..self.units.len())
.map(|unit_index| self.unit_max_in_flight(unit_index).get())
.sum();
2 + max_in_flight.next_power_of_two().ilog2() as usize
}
}
#[hdl(get(|c| c.out_reg_num_width))]
pub type CpuConfigOutRegNumWidth<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.unit_num_width()))]
pub type CpuConfigUnitNumWidth<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.p_reg_num_width()))]
pub type CpuConfigPRegNumWidth<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| 1 << c.out_reg_num_width))]
pub type CpuConfig2PowOutRegNumWidth<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.units.len()))]
pub type CpuConfigUnitCount<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.fetch_width.get()))]
pub type CpuConfigFetchWidth<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.fetch_width.get() * 2))]
pub type TwiceCpuConfigFetchWidth<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.max_branches_per_fetch.get()))]
pub type CpuConfigMaxBranchesPerFetch<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.max_fetches_in_flight.get()))]
pub type CpuConfigMaxFetchesInFlight<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.log2_fetch_width_in_bytes.into()))]
pub type CpuConfigLog2FetchWidthInBytes<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.fetch_width_in_bytes()))]
pub type CpuConfigFetchWidthInBytes<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.log2_fetches_per_cache_line()))]
pub type CpuConfigLog2FetchesPerCacheLine<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.fetches_per_cache_line()))]
pub type CpuConfigFetchesPerCacheLine<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.log2_cache_line_size_in_bytes.into()))]
pub type CpuConfigLog2CacheLineSizeInBytes<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.cache_line_size_in_bytes()))]
pub type CpuConfigCacheLineSizeInBytes<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.log2_l1_i_cache_line_count.into()))]
pub type CpuConfigLog2L1ICacheLineCount<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.l1_i_cache_line_count()))]
pub type CpuConfigL1ICacheLineCount<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.log2_l1_i_cache_size_in_bytes()))]
pub type CpuConfigLog2L1ICacheSizeInBytes<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.l1_i_cache_size_in_bytes()))]
pub type CpuConfigL1ICacheSizeInBytes<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.l1_i_cache_max_misses_in_flight.get()))]
pub type CpuConfigL1ICacheMaxMissesInFlight<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.rob_size.get()))]
pub type CpuConfigRobSize<C: PhantomConstGet<CpuConfig>> = DynSize;
/// the maximum of all [`unit_max_in_flight()`][CpuConfig::unit_max_in_flight()]
#[hdl(get(|c| c.max_unit_max_in_flight().get()))]
pub type CpuConfigMaxUnitMaxInFlight<C: PhantomConstGet<CpuConfig>> = DynSize;
pub trait PhantomConstCpuConfig:
PhantomConstGet<CpuConfig>
+ Into<PhantomConst<CpuConfig>>
+ From<PhantomConst<CpuConfig>>
+ Type
+ ToSimValue<Type = Self>
+ ToExpr<Type = Self>
+ HdlPartialOrdImpl<Self>
{
mod sealed {
pub trait Sealed {}
}
impl PhantomConstCpuConfig for PhantomConst<CpuConfig> {}
impl sealed::Sealed for PhantomConst<CpuConfig> {}
pub trait CpuConfigType: Type + ToExpr<Type = Self> + sealed::Sealed {
fn get(self) -> Interned<CpuConfig>;
}
impl CpuConfigType for PhantomConst<CpuConfig> {
fn get(self) -> Interned<CpuConfig> {
self.get()
}
}
pub trait Identity<Arg: ?Sized> {
type SelfType: ?Sized;
type ArgType: ?Sized;
}
impl<T: ?Sized, Arg: ?Sized> Identity<Arg> for T {
type SelfType = T;
type ArgType = Arg;
}
macro_rules! impl_cpu_config_accessors {
(
$(
#[without_generics = $without_generics:ident]
$vis:vis type $ident:ident<$T:ident> = |$arg:ident| $expr:expr;
)*
) => {
$(
#[allow(non_camel_case_types)]
$vis struct $without_generics;
#[allow(non_upper_case_globals)]
$vis const $ident: $without_generics = $without_generics;
$vis type $ident<$T> = <DynSize as Identity<$T>>::SelfType;
impl<$T: CpuConfigType> std::ops::Index<$T> for $without_generics {
type Output = usize;
fn index(&self, $arg: $T) -> &Self::Output {
Interned::into_inner(Intern::intern_sized($expr))
}
}
)*
};
}
impl_cpu_config_accessors! {
#[without_generics = __UnitNumWidth_WithoutGenerics]
pub type UnitNumWidth<T> = |arg| arg.get().unit_num_width();
#[without_generics = __UnitOutRegNumWidth_WithoutGenerics]
pub type UnitOutRegNumWidth<T> = |arg| arg.get().out_reg_num_width;
#[without_generics = __PRegNumWidth_WithoutGenerics]
pub type PRegNumWidth<T> = |arg| PRegNum[arg].canonical().bit_width();
#[without_generics = __RetireQueueIndexWidth_WithoutGenerics]
pub type RetireQueueIndexWidth<T> = |arg| arg.get().retire_queue_index_width();
#[without_generics = __UnitCount_WithoutGenerics]
pub type UnitCount<T> = |arg| arg.get().non_const_unit_nums().len();
#[without_generics = __FetchWidth_WithoutGenerics]
pub type FetchWidth<T> = |arg| arg.get().fetch_width.get();
}

View file

@ -1,4 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
pub mod simple_power_isa;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -25,514 +25,28 @@ pub struct PowerIsaCrBitNum {
pub bit_in_field: UInt<2>,
}
macro_rules! suffix_str_with_0_to_31 {
($str:literal) => {
suffix_str_with_0_to_31!(
$str,
[
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29, 30, 31,
]
)
};
($str:literal, [$($num:literal),* $(,)?]) => {
[$(concat!($str, $num)),*]
};
}
const fn fill_names_range<'a>(dest: &mut [Option<&'a str>], range: Range<u32>, src: &[&'a str]) {
assert!((range.end - range.start) as usize == src.len());
let mut i = 0;
while i < src.len() {
dest[i + range.start as usize] = Some(src[i]);
i += 1;
}
}
macro_rules! power_isa_regs {
(
$(
#[name_str = $name_str:literal, expr_fn = $expr_fn:ident, sim_fn = $sim_fn:ident]
$(#[doc = $($doc:tt)*])*
pub const $REG_NUM:ident: u32 = $reg_num_expr:expr;
)*
$(
#[names_fn = |$names_var:ident| $names_body:expr $(,
reg_num_fn = $reg_num_fn_multi:ident,
expr_fn = $expr_fn_multi:ident($fn_multi_arg:ty),
expr_imm_fn = $expr_imm_fn_multi:ident,
sim_fn = $sim_fn_multi:ident,)?
]
$(#[doc = $($doc_multi:tt)*])*
pub const $REG_NUM_MULTI:ident: Range<u32> = $reg_num_multi_expr:expr;
)*
) => {
impl MOpRegNum {
pub const POWER_ISA_REG_NAMES: &[Option<&str>; 1 << Self::WIDTH] = &{
let mut retval = [None; _];
$(retval[Self::$REG_NUM as usize] = Some($name_str);)*
$({
let $names_var = &mut retval;
$names_body
})*
retval
};
$(
$(#[doc = $($doc)*])*
pub const $REG_NUM: u32 = $reg_num_expr;
$(#[doc = $($doc)*])*
#[hdl]
pub fn $expr_fn() -> Expr<Self> {
#[hdl]
Self {
value: Self::$REG_NUM.cast_to_static::<UInt<_>>(),
}
}
$(#[doc = $($doc)*])*
#[hdl]
pub fn $sim_fn() -> SimValue<Self> {
#[hdl(sim)]
Self {
value: Self::$REG_NUM.cast_to_static::<UInt<_>>(),
}
}
)*
$(
$(#[doc = $($doc_multi)*])*
pub const $REG_NUM_MULTI: Range<u32> = $reg_num_multi_expr;
power_isa_regs! {
@helper_fns
#[names_fn = |$names_var| $names_body $(,
reg_num_fn = $reg_num_fn_multi,
expr_fn = $expr_fn_multi($fn_multi_arg),
expr_imm_fn = $expr_imm_fn_multi,
sim_fn = $sim_fn_multi,)?
]
$(#[doc = $($doc_multi)*])*
pub const $REG_NUM_MULTI: Range<u32> = $reg_num_multi_expr;
}
)*
}
};
(
@helper_fns
#[names_fn = |$names_var:ident| $names_body:expr]
$(#[doc = $($doc_multi:tt)*])*
pub const $REG_NUM_MULTI:ident: Range<u32> = $reg_num_multi_expr:expr;
) => {};
(
@helper_fns
#[
names_fn = |$names_var:ident| $names_body:expr,
reg_num_fn = $reg_num_fn_multi:ident,
expr_fn = $expr_fn_multi:ident($fn_multi_arg:ty),
expr_imm_fn = $expr_imm_fn_multi:ident,
sim_fn = $sim_fn_multi:ident,
]
$(#[doc = $($doc_multi:tt)*])*
pub const $REG_NUM_MULTI:ident: Range<u32> = $reg_num_multi_expr:expr;
) => {
$(#[doc = $($doc_multi)*])*
pub const fn $reg_num_fn_multi(index: usize) -> u32 {
range_u32_nth_or_panic(&Self::$REG_NUM_MULTI, index)
}
$(#[doc = $($doc_multi)*])*
#[hdl]
pub fn $expr_fn_multi(input: Expr<$fn_multi_arg>) -> Expr<Self> {
#[hdl]
Self {
value: (Self::$REG_NUM_MULTI.start + input).cast_to_static::<UInt<_>>(),
}
}
$(#[doc = $($doc_multi)*])*
#[hdl]
pub fn $expr_imm_fn_multi(input: usize) -> Expr<Self> {
#[hdl]
Self {
value: Self::$reg_num_fn_multi(input).cast_to_static::<UInt<_>>(),
}
}
$(#[doc = $($doc_multi)*])*
#[hdl]
pub fn $sim_fn_multi(reg_num: &SimValue<$fn_multi_arg>) -> SimValue<Self> {
#[hdl(sim)]
Self {
value: (Self::$REG_NUM_MULTI.start + reg_num).cast_to_static::<UInt<_>>(),
}
}
};
}
power_isa_regs! {
#[name_str = "lr", expr_fn = power_isa_lr_reg, sim_fn = power_isa_lr_reg_sim]
pub const POWER_ISA_LR_REG_NUM: u32 = 1;
#[name_str = "ctr", expr_fn = power_isa_ctr_reg, sim_fn = power_isa_ctr_reg_sim]
pub const POWER_ISA_CTR_REG_NUM: u32 = 2;
#[name_str = "tar", expr_fn = power_isa_tar_reg, sim_fn = power_isa_tar_reg_sim]
pub const POWER_ISA_TAR_REG_NUM: u32 = 3;
#[name_str = "xer[so,ov,ov32]", expr_fn = power_isa_xer_so_ov_ov32_reg, sim_fn = power_isa_xer_so_ov_ov32_reg_sim]
/// SO, OV, and OV32 XER bits -- in [`PRegValue.flags`]
///
/// [`PRegValue.flags`]: struct@crate::register::PRegValue
pub const POWER_ISA_XER_SO_OV_OV32_REG_NUM: u32 =
range_u32_nth_or_panic(&Self::FLAG_REG_NUMS, 0);
#[name_str = "xer[ca,ca32]", expr_fn = power_isa_xer_ca_ca32_reg, sim_fn = power_isa_xer_ca_ca32_reg_sim]
/// CA and CA32 XER bits -- in [`PRegValue.flags`]
///
/// [`PRegValue.flags`]: struct@crate::register::PRegValue
pub const POWER_ISA_XER_CA_CA32_REG_NUM: u32 = 4;
#[name_str = "xer[other]", expr_fn = power_isa_xer_other_reg, sim_fn = power_isa_xer_other_reg_sim]
/// only the XER bits that don't exist in [`PRegValue.flags`]
///
/// [`PRegValue.flags`]: struct@crate::register::PRegValue
pub const POWER_ISA_XER_OTHER_REG_NUM: u32 = 5;
#[name_str = "temp", expr_fn = power_isa_temp_reg, sim_fn = power_isa_temp_reg_sim]
/// used as a temporary for things like computing the effective address before loading/storing memory
pub const POWER_ISA_TEMP_REG_NUM: u32 = 8;
#[name_str = "cr0", expr_fn = power_isa_cr_0_reg, sim_fn = power_isa_cr_0_reg_sim]
pub const POWER_ISA_CR_0_REG_NUM: u32 = range_u32_nth_or_panic(&Self::FLAG_REG_NUMS, 1);
#[names_fn = |names| {
fill_names_range(names, Self::POWER_ISA_CR_1_THRU_7_REG_NUMS, &["cr1", "cr2", "cr3", "cr4", "cr5", "cr6", "cr7"]);
}]
pub const POWER_ISA_CR_1_THRU_7_REG_NUMS: Range<u32> = 9..16;
#[
names_fn = |names| {
fill_names_range(names, Self::POWER_ISA_GPR_REG_NUMS, &suffix_str_with_0_to_31!("r"));
},
reg_num_fn = power_isa_gpr_reg_num,
expr_fn = power_isa_gpr_reg(UInt<5>),
expr_imm_fn = power_isa_gpr_reg_imm,
sim_fn = power_isa_gpr_reg_sim,
]
pub const POWER_ISA_GPR_REG_NUMS: Range<u32> = 32..64;
#[
names_fn = |names| {
fill_names_range(names, Self::POWER_ISA_FPR_REG_NUMS, &suffix_str_with_0_to_31!("f"));
},
reg_num_fn = power_isa_fpr_reg_num,
expr_fn = power_isa_fpr_reg(UInt<5>),
expr_imm_fn = power_isa_fpr_reg_imm,
sim_fn = power_isa_fpr_reg_sim,
]
pub const POWER_ISA_FPR_REG_NUMS: Range<u32> = 64..96;
}
impl MOpRegNum {
pub const POWER_ISA_LR_REG_NUM: u32 = 1;
pub const POWER_ISA_CTR_REG_NUM: u32 = 2;
pub const POWER_ISA_TAR_REG_NUM: u32 = 3;
/// XER bits are stored in [`PRegValue.flags`], bits that don't exist in [`PRegValue.flags`] are stored in [`PRegValue.int_fp`]
///
/// [`PRegValue.flags`]: struct@crate::register::PRegValue
/// [`PRegValue.int_fp`]: struct@crate::register::PRegValue
pub const POWER_ISA_XER_REG_NUM: u32 = 4;
pub const POWER_ISA_CR_REG_NUMS: Range<u32> = 8..16;
pub const fn power_isa_cr_reg_num(index: usize) -> u32 {
if index == 0 {
Self::POWER_ISA_CR_0_REG_NUM
} else {
range_u32_nth_or_panic(&Self::POWER_ISA_CR_1_THRU_7_REG_NUMS, index - 1)
}
}
#[hdl]
pub fn power_isa_cr_reg(field_num: Expr<UInt<3>>) -> Expr<Self> {
#[hdl]
let power_isa_cr_reg: Self = wire();
#[hdl]
if field_num.cmp_eq(0u8) {
connect_any(power_isa_cr_reg.value, Self::POWER_ISA_CR_0_REG_NUM);
} else {
connect_any(
power_isa_cr_reg.value,
Self::POWER_ISA_CR_1_THRU_7_REG_NUMS.start - 1 + field_num,
);
}
power_isa_cr_reg
}
#[hdl]
pub fn power_isa_cr_reg_imm(index: usize) -> Expr<Self> {
#[hdl]
Self {
value: Self::power_isa_cr_reg_num(index).cast_to_static::<UInt<_>>(),
}
}
#[hdl]
pub fn power_isa_cr_reg_sim(field_num: &SimValue<UInt<3>>) -> SimValue<Self> {
#[hdl(sim)]
Self {
value: Self::power_isa_cr_reg_num(
field_num.cast_to_static::<UInt<8>>().as_int() as usize
)
.cast_to_static::<UInt<_>>(),
}
range_u32_nth_or_panic(&Self::POWER_ISA_CR_REG_NUMS, index)
}
pub const fn power_isa_gpr_or_zero_reg_num(index: usize) -> u32 {
if index == 0 {
Self::CONST_ZERO_REG_NUM
} else {
Self::power_isa_gpr_reg_num(index)
}
pub const POWER_ISA_GPR_REG_NUMS: Range<u32> = 32..64;
pub const fn power_isa_gpr_reg_num(index: usize) -> u32 {
range_u32_nth_or_panic(&Self::POWER_ISA_GPR_REG_NUMS, index)
}
#[hdl]
pub fn power_isa_gpr_or_zero_reg(reg_num: Expr<UInt<5>>) -> Expr<Self> {
#[hdl]
let power_isa_gpr_or_zero_reg: Self = wire();
connect(power_isa_gpr_or_zero_reg, Self::power_isa_gpr_reg(reg_num));
#[hdl]
if reg_num.cmp_eq(0u8) {
connect(power_isa_gpr_or_zero_reg, Self::const_zero());
}
power_isa_gpr_or_zero_reg
}
#[hdl]
pub fn power_isa_gpr_or_zero_reg_imm(index: usize) -> Expr<Self> {
#[hdl]
Self {
value: Self::power_isa_gpr_or_zero_reg_num(index).cast_to_static::<UInt<_>>(),
}
}
#[hdl]
pub fn power_isa_gpr_or_zero_reg_sim(reg_num: &SimValue<UInt<5>>) -> SimValue<Self> {
#[hdl(sim)]
Self {
value: Self::power_isa_gpr_or_zero_reg_num(
reg_num.cast_to_static::<UInt<8>>().as_int() as usize,
)
.cast_to_static::<UInt<_>>(),
}
}
}
#[hdl(cmp_eq)]
pub struct PowerIsaSpr {
pub num: UInt<10>,
}
macro_rules! make_spr_enum {
(
$(#[$enum_meta:meta])*
$enum_vis:vis enum $PowerIsaSprEnum:ident {
$($enum_body:tt)*
}
) => {
$(#[$enum_meta])*
$enum_vis enum $PowerIsaSprEnum {
$($enum_body)*
Unknown(u16),
}
make_spr_enum! {
@impl
$enum_vis enum $PowerIsaSprEnum {
$($enum_body)*
}
}
};
(
@impl
$enum_vis:vis enum $PowerIsaSprEnum:ident {
$(
$(#[$variant_meta:meta])*
$Variant:ident = $value:literal,
)+
}
) => {
impl $PowerIsaSprEnum {
pub const VARIANTS: &[Self; 1 << 10] = &{
let mut retval = [Self::Unknown(0); 1 << 10];
let mut i = 0;
while i < retval.len() {
retval[i] = Self::Unknown(i as u16);
i += 1;
}
let mut last_value = None;
#[track_caller]
const fn add_variant(
values: &mut [$PowerIsaSprEnum; 1 << 10],
last_value: &mut Option<u16>,
variant: $PowerIsaSprEnum,
value: u16,
) {
assert!(value < 1 << 10, "variant value out of range");
if let Some(last_value) = *last_value {
assert!(last_value < value, "variants must be in ascending order with no duplicates");
}
*last_value = Some(value);
values[value as usize] = variant;
}
$(add_variant(&mut retval, &mut last_value, Self::$Variant, $value);)+
retval
};
pub const fn value(self) -> u16 {
match self {
$(Self::$Variant => $value,)+
Self::Unknown(v) => v,
}
}
}
};
}
make_spr_enum! {
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[repr(u16)]
pub enum PowerIsaSprEnum {
Xer = 1,
UserDscr = 3,
Lr = 8,
Ctr = 9,
UserAmr = 13,
Dscr = 17,
Dsisr = 18,
Dar = 19,
Dec = 22,
Srr0 = 26,
Srr1 = 27,
Cfar = 28,
Amr = 29,
Pidr = 48,
Iamr = 61,
ReadCtrl = 136,
WriteCtrl = 152,
Fscr = 153,
Uamor = 157,
Pspb = 159,
Dpdes = 176,
Dawr0 = 180,
Dawr1 = 181,
Rpr = 186,
Ciabr = 187,
Dawrx0 = 188,
Dawrx1 = 189,
Hfscr = 190,
Vrsave = 256,
UserSprg3 = 259,
Tb = 268,
Tbu = 269,
Sprg0 = 272,
Sprg1 = 273,
Sprg2 = 274,
Sprg3 = 275,
Tbl = 284,
WriteTbu = 285,
Tbu40 = 286,
Pvr = 287,
Hsprg0 = 304,
Hsprg1 = 305,
Hdsisr = 306,
Hdar = 307,
Spurr = 308,
Purr = 309,
Hdec = 310,
Hrmor = 313,
Hsrr0 = 314,
Hsrr1 = 315,
Lpcr = 318,
Lpidr = 319,
Hmer = 336,
Hmeer = 337,
Pcr = 338,
Heir = 339,
Amor = 349,
Tir = 446,
UserHdexcr = 455,
Ptcr = 464,
Hashkeyr = 468,
Hashpkeyr = 469,
Hdexcr = 471,
Usprg0 = 496,
Usprg1 = 497,
Urmor = 505,
Usrr0 = 506,
Usrr1 = 507,
Smfctrl = 511,
UserSier2 = 736,
UserSier3 = 737,
UserMmcr3 = 738,
Sier2 = 752,
Sier3 = 753,
Mmcr3 = 754,
UserSier = 768,
Mmcr2 = 769,
Mmcra = 770,
Pmc1 = 771,
Pmc2 = 772,
Pmc3 = 773,
Pmc4 = 774,
Pmc5 = 775,
Pmc6 = 776,
Mmcr0 = 779,
Siar = 780,
Sdar = 781,
Mmcr1 = 782,
Sier = 784,
PrivMmcr2 = 785,
PrivMmcra = 786,
PrivPmc1 = 787,
PrivPmc2 = 788,
PrivPmc3 = 789,
PrivPmc4 = 790,
PrivPmc5 = 791,
PrivPmc6 = 792,
PrivMmcr0 = 795,
PrivSiar = 796,
PrivSdar = 797,
PrivMmcr1 = 798,
Bescrs15 = 800,
Bescrsu16 = 801,
Bescrr15 = 802,
Bescrru16 = 803,
Ebbhr = 804,
Ebbrr = 805,
Bescr = 806,
Reserved808 = 808,
Reserved809 = 809,
Reserved810 = 810,
Reserved811 = 811,
UserDexcr = 812,
Tar = 815,
Asdr = 816,
Psscr = 823,
Dexcr = 828,
Ic = 848,
Vtb = 849,
HyperPsscr = 855,
Ppr = 896,
Ppr32 = 898,
Pir = 1023,
}
}
impl ValueType for PowerIsaSprEnum {
type Type = PowerIsaSpr;
type ValueCategory = fayalite::expr::value_category::ValueCategoryValue;
fn ty(&self) -> Self::Type {
PowerIsaSpr
}
}
impl ToExpr for PowerIsaSprEnum {
#[hdl]
fn to_expr(&self) -> Expr<Self::Type> {
#[hdl]
PowerIsaSpr {
num: self.value().cast_to_static::<UInt<_>>(),
}
}
}
impl ToSimValueWithType<PowerIsaSpr> for PowerIsaSprEnum {
fn to_sim_value_with_type(&self, _ty: PowerIsaSpr) -> SimValue<PowerIsaSpr> {
self.to_sim_value()
}
}
impl ToSimValue for PowerIsaSprEnum {
#[hdl]
fn to_sim_value(&self) -> SimValue<Self::Type> {
#[hdl(sim)]
PowerIsaSpr {
num: self.value().cast_to_static::<UInt<_>>(),
}
pub const POWER_ISA_FPR_REG_NUMS: Range<u32> = 64..96;
pub const fn power_isa_fpr_reg_num(index: usize) -> u32 {
range_u32_nth_or_panic(&Self::POWER_ISA_FPR_REG_NUMS, index)
}
}

View file

@ -0,0 +1,261 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
config::{CpuConfig, CpuConfigType, FetchWidth, PRegNumWidth},
instruction::{MOp, MOpDestReg, MOpRegNum, MOpTrait, MoveRegMOp, PRegNum, RenamedMOp},
rename_table::{rename_table, RenameTablePortConfig},
unit::{RenamedInsnData, RetireQueueIndex, UnitMOp},
util::array_vec::{ArrayVec, Length, ReadyValidArray},
};
use fayalite::{
prelude::*,
util::{prefix_sum::PrefixSumAlgorithm, ready_valid::ReadyValid},
};
#[hdl(no_static)]
pub struct InstructionRenameInputInsn<C: Type + CpuConfigType> {
pub mop: MOp,
pub pc: UInt<64>,
pub renamed_dest: PRegNum<C>,
}
#[hdl(no_static)]
struct InsnsInPrefixSummary<C: Type + CpuConfigType> {
all_ready: Bool,
ready_count: Length<FetchWidth<C>>,
retire_queue_used: Length<FetchWidth<C>>,
config: C,
}
type C = PhantomConst<CpuConfig>;
#[hdl]
pub type InstructionRenameInsnsOut<C: Type + CpuConfigType> = ArrayType<
ReadyValid<RenamedInsnData<C, RenamedMOp<PRegNumWidth<C>>, PRegNum<C>>>,
FetchWidth<C>,
>;
#[hdl_module]
pub fn instruction_rename(config: PhantomConst<CpuConfig>) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let insns_in: ReadyValidArray<InstructionRenameInputInsn<C>, FetchWidth<C>> =
m.input(ReadyValidArray[InstructionRenameInputInsn[config]][FetchWidth[config]]);
#[hdl]
let start_retire_queue_index: RetireQueueIndex<C> = m.input(RetireQueueIndex[config]);
#[hdl]
let end_retire_queue_index: RetireQueueIndex<C> = m.output(RetireQueueIndex[config]);
#[hdl]
let insns_out: InstructionRenameInsnsOut<C> = m.output(InstructionRenameInsnsOut[config]);
// TODO: handle resetting table after cancelling instructions
#[hdl]
let insns_ready_or_move = wire(ArrayType[Bool][FetchWidth[config]]);
for (insn_ready_or_move, insn_out) in insns_ready_or_move.into_iter().zip(insns_out) {
connect(insn_ready_or_move, insn_out.ready);
}
ArrayVec::for_each(insns_in.data, |fetch_index, input_insn| {
#[hdl]
match input_insn.mop {
UnitMOp::<_, _, _>::TransformedMove(_) => {
connect(insns_ready_or_move[fetch_index], true);
}
UnitMOp::<_, _, _>::AluBranch(_) | UnitMOp::<_, _, _>::LoadStore(_) => {}
}
});
let insns_in_prefix_summary_ty = InsnsInPrefixSummary[config];
#[hdl]
let insns_in_prefix_summaries = wire(ArrayType[insns_in_prefix_summary_ty][FetchWidth[config]]);
let insns_in_prefix_summaries_vec = PrefixSumAlgorithm::WorkEfficient.run(
(0..FetchWidth[config]).map(|fetch_index| {
#[hdl]
let insns_in_prefix_summary_in = wire(insns_in_prefix_summary_ty);
#[hdl]
let InsnsInPrefixSummary::<_> {
all_ready,
ready_count,
retire_queue_used,
config: _,
} = insns_in_prefix_summary_in;
connect(all_ready, insns_out[fetch_index].ready);
connect(
ready_count,
Expr::ty(ready_count).cast_from_uint_unchecked(all_ready.cast_to(UInt[1])),
);
connect(retire_queue_used, Expr::ty(retire_queue_used).zero());
#[hdl]
if let HdlSome(input_insn) = ArrayVec::get(insns_in.data, fetch_index) {
connect(retire_queue_used, ready_count);
#[hdl]
match input_insn.mop {
UnitMOp::<_, _, _>::TransformedMove(_) => {
connect(all_ready, true);
}
UnitMOp::<_, _, _>::AluBranch(_) | UnitMOp::<_, _, _>::LoadStore(_) => {}
}
}
insns_in_prefix_summary_in
}),
|l, r| {
#[hdl]
let insns_in_prefix_summary_merge = wire(insns_in_prefix_summary_ty);
#[hdl]
let InsnsInPrefixSummary::<_> {
all_ready,
ready_count,
retire_queue_used,
config: _,
} = insns_in_prefix_summary_merge;
connect(all_ready, l.all_ready & r.all_ready);
#[hdl]
if l.all_ready {
connect(
ready_count,
Expr::ty(ready_count).cast_from_uint_unchecked(
Length::as_uint(l.ready_count) + Length::as_uint(r.ready_count),
),
);
connect(
retire_queue_used,
Expr::ty(retire_queue_used).cast_from_uint_unchecked(
Length::as_uint(l.retire_queue_used) + Length::as_uint(r.retire_queue_used),
),
);
} else {
connect(ready_count, l.ready_count);
connect(retire_queue_used, l.retire_queue_used);
}
insns_in_prefix_summary_merge
},
);
for (l, r) in insns_in_prefix_summaries
.into_iter()
.zip(insns_in_prefix_summaries_vec)
{
connect(l, r);
}
connect(
insns_in.ready,
insns_in_prefix_summaries[FetchWidth[config] - 1].ready_count,
);
#[hdl]
let retire_queue_indexes = wire(Array[RetireQueueIndex[config]][FetchWidth[config] + 1]);
connect(retire_queue_indexes[0], start_retire_queue_index);
connect(
end_retire_queue_index,
retire_queue_indexes[FetchWidth[config]],
);
for (retire_queue_index, insns_in_prefix_summary) in retire_queue_indexes
.into_iter()
.skip(1)
.zip(insns_in_prefix_summaries)
{
connect_any(
retire_queue_index.index,
start_retire_queue_index.index
+ Length::as_uint(insns_in_prefix_summary.retire_queue_used),
);
}
let mut port_configs = Vec::new();
let mut src_reg_count = 0;
MOpTrait::for_each_src_reg(MOp.uninit(), &mut |_, src_index| {
src_reg_count = src_reg_count.max(src_index + 1);
});
for _ in 0..FetchWidth[config] {
for _ in 0..src_reg_count {
port_configs.push(RenameTablePortConfig::Read {
addr_range: MOpRegNum::NON_CONST_REG_NUMS,
});
}
for dest_reg_kind in MOpDestReg::REG_KINDS {
port_configs.push(RenameTablePortConfig::Write {
addr_range: dest_reg_kind.reg_num_range(),
});
}
}
#[hdl]
let rename_table = instance(rename_table(config, &port_configs));
connect(rename_table.cd, cd);
for read_port in rename_table.read_ports {
connect_any(read_port.addr, 0_hdl_u0);
}
for write_port in rename_table.write_ports {
connect_any(write_port.addr, 0_hdl_u0);
connect_any(write_port.data, PRegNum[config].const_zero());
}
ArrayVec::for_each(
ReadyValidArray::firing_data(insns_in),
|fetch_index, input_insn| {
let read_port_index = fetch_index * src_reg_count;
let write_port_index = fetch_index * MOpDestReg::REG_COUNT;
#[hdl]
let InstructionRenameInputInsn::<_> {
mop,
pc,
renamed_dest,
} = input_insn;
let insn_out =
MOpTrait::map_regs(mop, (), PRegNumWidth[config], &mut |src_reg, src_index| {
connect(
rename_table.read_ports[read_port_index + src_index].addr,
src_reg.cast_bits_to(MOpRegNum),
);
rename_table.read_ports[read_port_index + src_index]
.data
.cast_to_bits()
});
for (i, dest_reg) in MOpDestReg::regs(MOpTrait::dest_reg(mop))
.into_iter()
.enumerate()
{
connect(
rename_table.write_ports[write_port_index + i].addr,
dest_reg,
);
connect(
rename_table.write_ports[write_port_index + i].data,
renamed_dest,
);
}
let insn_out = UnitMOp::try_with_transformed_move_op(
insn_out,
RenamedMOp[PRegNumWidth[config]].TransformedMove,
|insn_out: Expr<HdlOption<_>>, move_reg: Expr<MoveRegMOp<_, _>>| {
for i in 0..MOpDestReg::REG_COUNT {
// execute move by using same PRegNum as src[0] for dest
connect(
rename_table.write_ports[write_port_index + i].data,
move_reg.common.src[0].cast_bits_to(PRegNum[config]),
);
}
// move already executed, so remove it
connect(insn_out, Expr::ty(insn_out).HdlNone());
},
);
connect(
insns_out[fetch_index].data,
HdlOption::map(insn_out, |insn_out| {
#[hdl]
RenamedInsnData::<_, _, _> {
retire_queue_index: retire_queue_indexes[fetch_index],
pc,
dest: renamed_dest,
mop: insn_out,
}
}),
);
},
);
}

View file

@ -1,15 +1,11 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
pub mod config;
pub mod decoder;
pub mod fetch;
pub mod instruction;
pub mod main_memory_and_io;
pub mod next_pc;
pub mod powerisa_instructions_xml;
#[cfg(todo)]
pub mod instruction_rename;
pub mod reg_alloc;
pub mod register;
pub mod rename_execute_retire;
pub mod rename_table;
pub mod retire_queue;
pub mod unit;
pub mod util;

File diff suppressed because it is too large Load diff

View file

@ -1,618 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use std::num::{NonZeroU64, NonZeroUsize, Wrapping};
use crate::{
main_memory_and_io::{
AddressRange, MemoryInterface, MemoryInterfaceConfig, MemoryOperationErrorKind,
MemoryOperationFinish, MemoryOperationFinishKind, MemoryOperationKind,
MemoryOperationStart,
},
util::array_vec::ArrayVec,
};
use fayalite::{
int::UIntInRange,
prelude::*,
util::ready_valid::{ReadyValid, queue},
};
const TICKS_PER_BAUD: usize = 16;
const PRECISION_BITS: usize = 16;
#[hdl_module]
pub fn uart_clock_gen(
clock_input_properties: PhantomConst<peripherals::ClockInputProperties>,
baud_rate: f64,
) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let tick: Bool = m.output();
let divisor =
clock_input_properties.get().frequency.into_inner() / (baud_rate * TICKS_PER_BAUD as f64);
type NumeratorType = u128;
let numerator: NumeratorType = 1 << PRECISION_BITS;
let denominator: NumeratorType = (divisor * numerator as f64).round() as _;
let remainder_ty = UInt::range(0..denominator);
#[hdl]
let remainder_reg = reg_builder().clock_domain(cd).reset(remainder_ty.zero());
#[hdl]
let sum = wire((remainder_reg + numerator).ty());
connect_any(sum, remainder_reg + numerator);
#[hdl]
let tick_reg = reg_builder().clock_domain(cd).reset(false);
connect(tick_reg, false);
#[hdl]
let next_remainder = wire(remainder_ty);
connect(remainder_reg, next_remainder);
#[hdl]
if sum.cmp_ge(denominator) {
connect_any(next_remainder, sum - denominator);
connect(tick_reg, true);
} else {
connect(next_remainder, sum);
}
connect(tick, tick_reg);
}
#[hdl_module]
pub fn transmitter() {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let tick: Bool = m.input();
#[hdl]
let tx: Bool = m.output();
#[hdl]
let input_byte: ReadyValid<UInt<8>> = m.input();
#[hdl]
let not_tx_reg: Bool = reg_builder().clock_domain(cd).reset(false);
connect(tx, !not_tx_reg);
#[hdl]
let input_buf_reg: HdlOption<UInt<8>> = reg_builder().clock_domain(cd).reset(HdlNone());
const BYTE_ON_WIRE_WIDTH: usize = 10;
#[hdl]
let shift_reg: Array<HdlOption<Bool>, _> = reg_builder()
.clock_domain(cd)
.reset([HdlNone(); BYTE_ON_WIRE_WIDTH]);
#[hdl]
if let HdlSome(v) = shift_reg[0] {
connect(not_tx_reg, !v);
} else {
connect(not_tx_reg, false);
}
#[hdl]
let tick_count_reg = reg_builder()
.clock_domain(cd)
.reset(0u8.cast_to_static::<UIntInRange<0, { TICKS_PER_BAUD }>>());
#[hdl]
let next_tick_count = wire(tick_count_reg.ty());
connect(tick_count_reg, next_tick_count);
#[hdl]
if !tick {
connect(next_tick_count, tick_count_reg);
} else if tick_count_reg.cmp_ge(TICKS_PER_BAUD - 1) {
connect(next_tick_count, 0u8.cast_to(next_tick_count.ty()));
} else {
connect(
next_tick_count,
(tick_count_reg.cast_to(UInt[tick_count_reg.ty().bit_width()]) + 1u8)
.cast_to(next_tick_count.ty()),
);
}
#[hdl]
if tick & tick_count_reg.cmp_eq(0u8) {
#[hdl]
if let HdlSome(_) = shift_reg[0] {
for v in shift_reg.windows(2) {
connect(v[0], v[1]);
}
connect(shift_reg.last().expect("known to be non-empty"), HdlNone());
}
#[hdl]
if let HdlNone = shift_reg[1] {
// when shift_reg[1] is HdlNone that means shift_reg would be completely empty in the next clock
// cycle, so instead fill it with the next byte if there is one.
#[hdl]
if let HdlSome(byte) = input_buf_reg {
connect(input_buf_reg, HdlNone());
connect(shift_reg, [HdlSome(true); _]);
connect(shift_reg[0], HdlSome(false));
for (i, v) in (*shift_reg)[1..][..8].iter().enumerate() {
connect(v, HdlSome(byte[i]));
}
}
}
}
#[hdl]
if let HdlSome(_) = input_buf_reg {
connect(input_byte.ready, false);
} else {
connect(input_byte.ready, true);
connect(input_buf_reg, input_byte.data);
}
}
/// `rx_synchronized` is [`peripherals::Uart::rx`], but after being synchronized to `cd.clk`.
/// `output_byte` is `HdlSome` for 1 clock cycle for every received byte.
#[hdl_module]
pub fn receiver_no_queue() {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let tick: Bool = m.input();
#[hdl]
let rx_synchronized: Bool = m.input();
#[hdl]
let output_byte: HdlOption<UInt<8>> = m.output();
const BITS_PER_BYTE: usize = 8;
#[hdl]
let output_byte_reg: HdlOption<UInt<{ BITS_PER_BYTE }>> =
reg_builder().clock_domain(cd).reset(HdlNone());
connect(output_byte, output_byte_reg);
connect(output_byte_reg, HdlNone()); // by default always reset to HdlNone()
#[hdl]
let tick_count_reg = reg_builder()
.clock_domain(cd)
.reset(0u8.cast_to(UInt::range(0..TICKS_PER_BAUD)));
#[hdl]
let shift_reg: UInt<{ BITS_PER_BYTE }> = reg_builder().clock_domain(cd).reset(0u8);
#[hdl]
enum State {
WaitingForStartBit,
StartBit,
DataBits(UIntInRange<0, { BITS_PER_BYTE }>),
StopBit,
}
#[hdl]
let state_reg = reg_builder()
.clock_domain(cd)
.reset(State.WaitingForStartBit());
#[hdl]
if tick {
#[hdl]
match state_reg {
State::WaitingForStartBit => {
connect(tick_count_reg, tick_count_reg.ty().zero());
#[hdl]
if !rx_synchronized {
connect(state_reg, State.StartBit());
}
}
State::StartBit =>
{
#[hdl]
if tick_count_reg.cmp_eq(TICKS_PER_BAUD - 1) {
connect(tick_count_reg, tick_count_reg.ty().zero());
connect(state_reg, State.DataBits(0u8.cast_to(State.DataBits)));
} else {
connect_any(tick_count_reg, tick_count_reg + 1u8);
}
}
State::DataBits(count) => {
#[hdl]
if tick_count_reg.cmp_eq(TICKS_PER_BAUD - 1) {
connect(tick_count_reg, tick_count_reg.ty().zero());
#[hdl]
if count.cmp_eq(BITS_PER_BYTE - 1) {
connect(state_reg, State.StopBit());
connect(output_byte_reg, HdlSome(shift_reg));
} else {
connect(
state_reg,
State.DataBits((count.cast_to(UInt[8]) + 1u8).cast_to(State.DataBits)),
);
}
} else {
connect_any(tick_count_reg, tick_count_reg + 1u8);
}
#[hdl]
if tick_count_reg.cmp_eq(TICKS_PER_BAUD / 2) {
connect_any(
shift_reg,
(shift_reg >> 1) | (rx_synchronized.cast_to_static::<UInt<1>>() << 7),
);
}
}
State::StopBit => {
#[hdl]
if tick_count_reg.cmp_eq(TICKS_PER_BAUD / 2) {
// go to WaitingForStartBit in the middle of the stop bit so we can adjust if the sender is faster than us.
connect(tick_count_reg, tick_count_reg.ty().zero());
connect(state_reg, State.WaitingForStartBit());
} else {
connect_any(tick_count_reg, tick_count_reg + 1u8);
}
}
}
}
}
#[hdl]
pub enum ReceiverQueueStatus {
QueueEmpty,
QueueNotEmpty,
QueueAlmostFull,
QueueFull,
QueueOverflowed,
}
impl ReceiverQueueStatus {
pub fn sim_as_u8(this: impl ToSimValue<Type = Self>) -> u8 {
SimValue::bits(&this.into_sim_value())
.cast_to_static::<UInt<8>>()
.as_int()
}
#[hdl]
pub fn for_queue_len_sim(current_count: usize, queue_size: NonZeroUsize) -> SimValue<Self> {
if current_count == 0 {
#[hdl(sim)]
ReceiverQueueStatus.QueueEmpty()
} else if current_count == queue_size.get() {
#[hdl(sim)]
ReceiverQueueStatus.QueueFull()
} else if current_count == queue_size.get() - 1 {
#[hdl(sim)]
ReceiverQueueStatus.QueueAlmostFull()
} else if current_count > queue_size.get() {
#[hdl(sim)]
ReceiverQueueStatus.QueueOverflowed()
} else {
#[hdl(sim)]
ReceiverQueueStatus.QueueNotEmpty()
}
}
#[hdl]
pub fn for_queue_len_as_u8(current_count: usize, queue_size: NonZeroUsize) -> u8 {
Self::sim_as_u8(Self::for_queue_len_sim(current_count, queue_size))
}
}
/// `rx_synchronized` is [`peripherals::Uart::rx`], but after being synchronized to `cd.clk`.
#[hdl_module]
pub fn receiver(queue_capacity: NonZeroUsize) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let tick: Bool = m.input();
#[hdl]
let rx_synchronized: Bool = m.input();
#[hdl]
let output_byte: ReadyValid<UInt<8>> = m.output();
#[hdl]
let queue_status: ReceiverQueueStatus = m.output();
#[hdl]
let queue = instance(queue(UInt::<8>::new_static(), queue_capacity, false, false));
connect(queue.cd, cd);
connect(output_byte, queue.out);
#[hdl]
let queue_overflowed_reg = reg_builder().clock_domain(cd).reset(false);
#[hdl]
if queue_overflowed_reg {
connect(queue_status, ReceiverQueueStatus.QueueOverflowed());
} else if queue.count.cmp_eq(0u8) {
connect(queue_status, ReceiverQueueStatus.QueueEmpty());
} else if queue.count.cmp_eq(queue_capacity) {
connect(queue_status, ReceiverQueueStatus.QueueFull());
} else if queue.count.cmp_eq(queue_capacity.get() - 1) {
connect(queue_status, ReceiverQueueStatus.QueueAlmostFull());
} else {
connect(queue_status, ReceiverQueueStatus.QueueNotEmpty());
}
#[hdl]
if ReadyValid::firing(output_byte) {
// clear overflow when a byte is read from the queue
connect(queue_overflowed_reg, false);
}
// we ignore queue.inp.ready other than noting an overflow when there was data but the queue wasn't ready
#[hdl]
if !queue.inp.ready {
#[hdl]
if let HdlSome(_) = queue.inp.data {
connect(queue_overflowed_reg, true);
}
}
#[hdl]
let inner = instance(receiver_no_queue());
connect(inner.cd, cd);
connect(inner.tick, tick);
connect(inner.rx_synchronized, rx_synchronized);
connect(queue.inp.data, inner.output_byte);
}
pub const SIMPLE_UART_RECEIVE_OFFSET: u64 = 0;
pub const SIMPLE_UART_TRANSMIT_OFFSET: u64 = 0;
pub const SIMPLE_UART_STATUS_OFFSET: u64 = 1;
pub const SIMPLE_UART_LOG2_BUS_WIDTH: u8 = 1;
pub const SIMPLE_UART_USED_SIZE: NonZeroU64 = NonZeroU64::new(2).expect("known non-zero");
pub const SIMPLE_UART_ADDRESS_SIZE: NonZeroU64 = NonZeroU64::new(1 << 6).expect("known non-zero");
#[hdl(no_static)]
struct Operation<C: PhantomConstGet<MemoryInterfaceConfig>> {
start: MemoryOperationStart<C>,
finish: HdlOption<MemoryOperationFinish<C>>,
}
pub const fn simple_uart_memory_interface_config(
op_id_width: usize,
start_address: Wrapping<u64>,
) -> MemoryInterfaceConfig {
assert!(
start_address
.0
.is_multiple_of(SIMPLE_UART_ADDRESS_SIZE.get()),
"start_address must be properly aligned"
);
MemoryInterfaceConfig {
log2_bus_width_in_bytes: SIMPLE_UART_LOG2_BUS_WIDTH,
queue_capacity: const { NonZeroUsize::new(1).unwrap() },
op_id_width,
address_range: AddressRange::Limited {
start: start_address,
size: SIMPLE_UART_ADDRESS_SIZE,
},
}
}
#[hdl_module]
pub fn simple_uart(
config: PhantomConst<MemoryInterfaceConfig>,
clock_input_properties: PhantomConst<peripherals::ClockInputProperties>,
baud_rate: f64,
receiver_queue_size: NonZeroUsize,
) {
let start_address = config.get().address_range.start();
assert_eq!(
*config.get(),
simple_uart_memory_interface_config(config.get().op_id_width, start_address),
);
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let memory_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
m.input(MemoryInterface[config]);
#[hdl]
let uart: peripherals::Uart = m.output();
#[hdl]
let rx_sync_intermediate_reg: Bool = reg_builder().clock_domain(cd).reset(true);
annotate(rx_sync_intermediate_reg, DontTouchAnnotation);
#[hdl]
let rx_sync_final_reg: Bool = reg_builder().clock_domain(cd).reset(true);
annotate(rx_sync_final_reg, DontTouchAnnotation);
connect(rx_sync_intermediate_reg, uart.rx);
connect(rx_sync_final_reg, rx_sync_intermediate_reg);
#[hdl]
let rx_synchronized: Bool = wire();
annotate(rx_synchronized, DontTouchAnnotation);
connect(rx_synchronized, rx_sync_final_reg);
#[hdl]
let clk_gen = instance(uart_clock_gen(clock_input_properties, baud_rate));
connect(clk_gen.cd, cd);
#[hdl]
let transmitter = instance(transmitter());
connect(transmitter.cd, cd);
connect(transmitter.tick, clk_gen.tick);
connect(uart.tx, transmitter.tx);
#[hdl]
let receiver = instance(receiver(receiver_queue_size));
connect(receiver.cd, cd);
connect(receiver.tick, clk_gen.tick);
connect(receiver.rx_synchronized, rx_synchronized);
#[hdl]
let operation_reg = reg_builder()
.clock_domain(cd)
.reset(HdlOption[Operation[config]].HdlNone());
let next_op_ids_ty = memory_interface.ty().next_op_ids.HdlSome;
#[hdl]
if let HdlSome(operation) = operation_reg {
#[hdl]
let MemoryInterface::<_> {
start,
finish,
next_op_ids,
config: _,
} = memory_interface;
connect(start.ready, false);
connect(finish.data, operation.finish);
connect(
next_op_ids,
HdlSome(ArrayVec::from_parts_unchecked(
repeat(operation.start.op_id, next_op_ids_ty.capacity()),
1u8.cast_to(next_op_ids_ty.len_ty()),
)),
);
connect(transmitter.input_byte.data, HdlNone());
connect(receiver.output_byte.ready, false);
#[hdl]
if let HdlSome(_) = operation.finish {
#[hdl]
if finish.ready {
connect(operation_reg, operation_reg.ty().HdlNone());
}
} else {
#[hdl]
let MemoryOperationStart::<_> {
kind,
addr,
write_data,
rw_mask,
op_id: _,
config: _,
} = operation.start;
#[hdl]
let valid_addr = wire();
connect(valid_addr, true);
#[hdl]
let all_ready = wire();
connect(all_ready, true);
#[hdl]
let read_data = wire(write_data.ty());
for (byte_index, ((rw_mask, write_data), read_data)) in rw_mask
.into_iter()
.zip(write_data)
.zip(read_data)
.enumerate()
{
let byte_addr = (addr | byte_index).cast_to_static::<UInt<64>>();
connect(read_data, 0u8);
#[hdl]
if rw_mask {
#[hdl]
match kind {
MemoryOperationKind::Read => {
#[hdl]
if byte_addr
.cmp_eq(start_address.0.wrapping_add(SIMPLE_UART_RECEIVE_OFFSET))
{
connect(receiver.output_byte.ready, valid_addr);
#[hdl]
if let HdlSome(byte) = receiver.output_byte.data {
connect(read_data, byte);
}
// if there is no byte ready yet, we read a zero to avoid blocking the CPU on external inputs.
} else if byte_addr
.cmp_eq(start_address.0.wrapping_add(SIMPLE_UART_STATUS_OFFSET))
{
connect(
read_data,
receiver
.queue_status
.cast_to_bits()
.cast_to_static::<UInt<8>>(),
);
} else {
connect(valid_addr, false);
}
}
MemoryOperationKind::Write => {
#[hdl]
if byte_addr
.cmp_eq(start_address.0.wrapping_add(SIMPLE_UART_TRANSMIT_OFFSET))
{
#[hdl]
if !transmitter.input_byte.ready {
connect(all_ready, false);
}
#[hdl]
if valid_addr {
connect(transmitter.input_byte.data, HdlSome(write_data));
}
} else {
connect(valid_addr, false);
}
}
}
}
}
#[hdl]
if !valid_addr {
connect(
operation_reg,
HdlSome(
#[hdl]
Operation::<_> {
start: operation.start,
finish: HdlSome(
#[hdl]
MemoryOperationFinish::<_> {
kind: MemoryOperationFinishKind
.Error(MemoryOperationErrorKind.Generic()),
read_data,
config,
},
),
},
),
);
} else if all_ready {
connect(
operation_reg,
HdlSome(
#[hdl]
Operation::<_> {
start: operation.start,
finish: HdlSome(
#[hdl]
MemoryOperationFinish::<_> {
kind: MemoryOperationFinishKind.Success(kind),
read_data,
config,
},
),
},
),
);
}
}
} else {
#[hdl]
let MemoryInterface::<_> {
start,
finish,
next_op_ids,
config: _,
} = memory_interface;
connect(start.ready, true);
connect(finish.data, finish.ty().data.HdlNone());
connect(
next_op_ids,
HdlSome(next_op_ids_ty.new_sim(next_op_ids_ty.element().zero())),
);
#[hdl]
if let HdlSome(start) = start.data {
connect(
operation_reg,
HdlSome(
#[hdl]
Operation::<_> {
start,
finish: operation_reg.ty().HdlSome.finish.HdlNone(),
},
),
);
}
connect(transmitter.input_byte.data, HdlNone());
connect(receiver.output_byte.ready, false);
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,24 +0,0 @@
%% SPDX-License-Identifier: LGPL-3.0-or-later
%% See Notices.txt for copyright information
stateDiagram-v2
direction LR
state "Next PC" as next_pc
[*] --> next_pc
state "Fetch/Decode" as fetch_decode
next_pc --> fetch_decode
state "Branch Predictor" as br_pred
next_pc --> br_pred
br_pred --> next_pc: cancel following
state "Post-decode" as post_decode
fetch_decode --> post_decode
br_pred --> post_decode
post_decode --> next_pc: cancel following
state "Execute/Retire" as execute_retire
post_decode --> execute_retire
execute_retire --> [*]
execute_retire --> next_pc: cancel following

File diff suppressed because it is too large Load diff

View file

@ -3,18 +3,18 @@
use crate::{
config::CpuConfig,
instruction::{
COMMON_MOP_SRC_LEN, MOp, MOpDestReg, MOpRegNum, MOpTrait, MoveRegMOp, PRegNum,
RenameTableName, UnitOutRegNum,
MOp, MOpDestReg, MOpRegNum, MOpTrait, MoveRegMOp, PRegNum, UnitOutRegNum,
COMMON_MOP_SRC_LEN,
},
unit::{
GlobalState, TrapData, UnitMOp, UnitOutput, UnitOutputWrite, UnitResult,
UnitResultCompleted, UnitTrait,
unit_base::{UnitForwardingInfo, UnitInput},
},
util::tree_reduce::tree_reduce_with_state,
util::array_vec::ReadyValidArray,
};
use fayalite::{
memory::{WriteStruct, splat_mask},
int::BoolOrIntType,
memory::{splat_mask, WriteStruct},
module::{instance_with_loc, memory_with_loc, wire_with_loc},
prelude::*,
util::ready_valid::ReadyValid,
@ -44,150 +44,12 @@ pub enum FetchDecodeSpecialOp {
#[hdl]
pub struct FetchDecodeInterface<FetchWidth: Size> {
pub decoded_insns: ArrayType<ReadyValid<FetchedDecodedMOp>, FetchWidth>,
pub decoded_insns: ReadyValidArray<FetchedDecodedMOp, FetchWidth>,
#[hdl(flip)]
pub fetch_decode_special_op: ReadyValid<FetchDecodeSpecialOp>,
}
#[hdl]
struct ROBRenamedInsn<UnitNumWidth: Size, OutRegNumWidth: Size> {
mop_dest: MOpDestReg,
p_dest: PRegNum<UnitNumWidth, OutRegNumWidth>,
}
#[hdl]
struct ROBEntry<UnitNumWidth: Size, OutRegNumWidth: Size> {
renamed_insn: ROBRenamedInsn<UnitNumWidth, OutRegNumWidth>,
dest_written: Bool,
}
#[hdl_module]
fn rob(config: &CpuConfig) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let renamed_insns_in: Array<ReadyValid<ROBRenamedInsn<DynSize, DynSize>>> = m.input(
Array[ReadyValid[ROBRenamedInsn[config.unit_num_width()][config.out_reg_num_width]]]
[config.fetch_width.get()],
);
#[hdl]
let unit_forwarding_info: UnitForwardingInfo<DynSize, DynSize, DynSize> =
m.input(config.unit_forwarding_info());
let rob_entry_ty = ROBEntry[config.unit_num_width()][config.out_reg_num_width];
#[hdl]
let rob = reg_builder()
.clock_domain(cd)
.no_reset(Array[rob_entry_ty][config.rob_size.get()]);
#[hdl]
let rob_valid_start = reg_builder()
.clock_domain(cd)
.reset(UInt::range(0..config.rob_size.get()).zero());
#[hdl]
let rob_valid_end = reg_builder()
.clock_domain(cd)
.reset(UInt::range(0..config.rob_size.get()).zero());
#[hdl]
let free_space = wire(UInt::range_inclusive(0..=config.rob_size.get()));
#[hdl]
if rob_valid_end.cmp_lt(rob_valid_start) {
// rob_valid_end wrapped around but start didn't
connect_any(
free_space,
rob_valid_end + config.rob_size.get() - rob_valid_start,
);
} else {
connect_any(free_space, rob_valid_end - rob_valid_start);
}
struct IndexAndRange {
index: Expr<UInt>,
range: std::ops::Range<usize>,
}
let mut next_write_index = IndexAndRange {
index: rob_valid_end,
range: 0..config.rob_size.get(),
};
for fetch_index in 0..config.fetch_width.get() {
let write_index = next_write_index;
let next_write_index_range = write_index.range.start..write_index.range.end + 1;
next_write_index = IndexAndRange {
index: wire_with_loc(
&format!("next_write_index_{fetch_index}"),
SourceLocation::caller(),
UInt::range(next_write_index_range.clone()),
),
range: next_write_index_range,
};
connect(
renamed_insns_in[fetch_index].ready,
fetch_index.cmp_lt(free_space),
);
#[hdl]
if let HdlSome(renamed_insn) = ReadyValid::firing_data(renamed_insns_in[fetch_index]) {
for i in write_index.range.clone() {
#[hdl]
if write_index.index.cmp_eq(i) {
connect(
rob[i % config.rob_size.get()],
#[hdl]
ROBEntry {
renamed_insn,
dest_written: false,
},
);
}
}
}
// TODO: optimize write_index chain better
connect_any(
next_write_index.index,
write_index.index
+ ReadyValid::firing(renamed_insns_in[fetch_index]).cast_to_static::<UInt<1>>(),
);
}
assert!(
config.rob_size >= config.fetch_width,
"rob_size ({}) is too small for fetch_width = {} -- next_write_index would overflow",
config.rob_size,
config.fetch_width,
);
#[hdl]
if next_write_index.index.cmp_lt(config.rob_size.get()) {
connect_any(rob_valid_end, next_write_index.index);
} else {
connect_any(
rob_valid_end,
next_write_index.index - config.rob_size.get(),
);
}
// TODO: optimize better, O(rob_size * unit_count) is too big here
for rob_index in 0..config.rob_size.get() {
for unit_index in 0..config.non_const_unit_nums().len() {
#[hdl]
if let HdlSome(unit_output_write) = unit_forwarding_info.unit_output_writes[unit_index]
{
#[hdl]
let UnitOutputWrite::<_> {
which: unit_out_reg,
value: _,
} = unit_output_write;
let p_reg_num = #[hdl]
PRegNum::<_, _> {
unit_num: config.unit_num().from_index(unit_index),
unit_out_reg,
};
#[hdl]
if rob[rob_index].renamed_insn.p_dest.cmp_eq(p_reg_num) {
connect(rob[rob_index].dest_written, true);
}
}
}
}
}
#[cfg(todo)]
#[hdl_module]
/// combination register allocator, register renaming, unit selection, and retire handling
pub fn reg_alloc(config: &CpuConfig) {
@ -205,10 +67,6 @@ pub fn reg_alloc(config: &CpuConfig) {
);
// TODO: finish
#[hdl]
let rob = instance(rob(config));
connect(rob.cd, cd);
let mut rename_table_mems = BTreeMap::<RenameTableName, MemBuilder<_>>::new();
for reg_kind in MOpDestReg::REG_KINDS {
@ -238,11 +96,6 @@ pub fn reg_alloc(config: &CpuConfig) {
#[hdl]
let renamed_mops_out_reg = wire(Array[HdlOption[config.p_reg_num()]][config.fetch_width.get()]);
for fetch_index in 0..config.fetch_width.get() {
// TODO: finish
connect(
rob.renamed_insns_in[fetch_index].data,
rob.ty().renamed_insns_in.element().data.HdlNone(),
);
// TODO: finish
connect(
fetch_decode_interface.decoded_insns[fetch_index].ready,
@ -263,7 +116,7 @@ pub fn reg_alloc(config: &CpuConfig) {
);
connect(
renamed_mops[fetch_index],
renamed_mops.ty().element().HdlNone(),
Expr::ty(renamed_mops).element().HdlNone(),
);
#[hdl]
struct RenameTableReadPort<T> {
@ -332,7 +185,7 @@ pub fn reg_alloc(config: &CpuConfig) {
let write_port = wire_with_loc(
&format!("{table_name}_{fetch_index}_{}", reg_kind.reg_name()),
SourceLocation::caller(),
write_port_.ty(),
Expr::ty(write_port_),
);
connect(write_port_, write_port);
write_ports.push_back(write_port);
@ -343,7 +196,7 @@ pub fn reg_alloc(config: &CpuConfig) {
addr: 0_hdl_u0,
en: false,
clk: cd.clk,
data: write_port.data.ty().uninit(),
data: Expr::ty(write_port.data).uninit(),
mask: splat_mask(config.p_reg_num(), true.to_expr()),
},
);
@ -375,7 +228,7 @@ pub fn reg_alloc(config: &CpuConfig) {
config.renamed_mop_in_unit().TransformedMove,
|renamed_mop, renamed_move_op: Expr<MoveRegMOp<_, _>>| {
// TODO: finish handling MoveRegMOp
connect(renamed_mop, renamed_mop.ty().HdlNone());
connect(renamed_mop, Expr::ty(renamed_mop).HdlNone());
},
);
connect(
@ -429,7 +282,7 @@ pub fn reg_alloc(config: &CpuConfig) {
);
connect(
selected_unit_index_leaf,
selected_unit_index_leaf.ty().HdlNone(),
Expr::ty(selected_unit_index_leaf).HdlNone(),
);
let unit_index_wire = wire_with_loc(
&format!("unit_index_{fetch_index}_{unit_index}"),
@ -447,7 +300,7 @@ pub fn reg_alloc(config: &CpuConfig) {
let selected_unit_index_node = wire_with_loc(
&format!("selected_unit_index_node_{fetch_index}_{state}"),
SourceLocation::caller(),
l.ty(),
Expr::ty(l),
);
*state += 1;
connect(selected_unit_index_node, l);
@ -483,7 +336,6 @@ pub fn reg_alloc(config: &CpuConfig) {
);
#[hdl]
let unit_forwarding_info = wire(config.unit_forwarding_info());
connect(rob.unit_forwarding_info, unit_forwarding_info);
for (unit_index, unit_config) in config.units.iter().enumerate() {
let dyn_unit = unit_config.kind.unit(config, unit_index);
let unit = instance_with_loc(
@ -516,7 +368,7 @@ pub fn reg_alloc(config: &CpuConfig) {
connect(unit_free_regs_tracker.alloc_out[0].ready, false);
connect(
unit_to_reg_alloc.input.data,
unit_to_reg_alloc.input.ty().data.HdlNone(),
Expr::ty(unit_to_reg_alloc.input).data.HdlNone(),
);
for fetch_index in 0..config.fetch_width.get() {
#[hdl]
@ -550,7 +402,7 @@ pub fn reg_alloc(config: &CpuConfig) {
} else {
connect(
unit_to_reg_alloc.input.data,
HdlSome(unit_to_reg_alloc.input.ty().data.HdlSome.uninit()),
HdlSome(Expr::ty(unit_to_reg_alloc.input).data.HdlSome.uninit()),
);
// FIXME: add hdl_assert(cd.clk, false.to_expr(), "");
}
@ -578,8 +430,7 @@ pub fn reg_alloc(config: &CpuConfig) {
connect(unit_to_reg_alloc.unit_forwarding_info, unit_forwarding_info);
connect(
unit_forwarding_info.unit_output_writes[unit_index],
unit_forwarding_info
.ty()
Expr::ty(unit_forwarding_info)
.unit_output_writes
.element()
.HdlNone(),

View file

@ -1,7 +1,10 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::util::tree_reduce::tree_reduce;
use fayalite::{module::wire_with_loc, prelude::*, util::ready_valid::ReadyValid};
use fayalite::{
module::wire_with_loc,
prelude::*,
util::{prefix_sum::reduce, ready_valid::ReadyValid},
};
use std::{num::NonZeroUsize, ops::Range};
#[hdl_module]
@ -44,7 +47,7 @@ pub fn unit_free_regs_tracker(
count,
count_overflowed,
alloc_nums,
}) = tree_reduce(
}) = reduce(
(0..reg_count).map(|index| Summary {
range: index..index + 1,
count: (!allocated_reg[index])
@ -73,7 +76,7 @@ pub fn unit_free_regs_tracker(
let reduced_alloc_nums = wire_with_loc(
&format!("reduced_alloc_nums_{}_{}", range.start, range.end),
SourceLocation::caller(),
Array[UInt[l.alloc_nums.ty().element().width() + 1]][alloc_at_once.get()],
Array[UInt[Expr::ty(l.alloc_nums).element().width() + 1]][alloc_at_once.get()],
);
for alloc_index in 0..alloc_at_once.get() {
#[hdl]
@ -120,7 +123,10 @@ pub fn unit_free_regs_tracker(
#[cfg(test)]
mod tests {
use super::*;
use fayalite::{firrtl::ExportOptions, module::transform::simplify_enums::SimplifyEnumsKind};
use fayalite::{
cli::FormalMode, firrtl::ExportOptions,
module::transform::simplify_enums::SimplifyEnumsKind, testing::assert_formal,
};
use std::num::NonZero;
fn test_unit_free_regs_tracker(
@ -195,7 +201,7 @@ mod tests {
}
}
#[hdl]
let free_before_alloc_array = wire(Array[free_reg.ty()][alloc_at_once.get() + 1]);
let free_before_alloc_array = wire(Array[Expr::ty(free_reg)][alloc_at_once.get() + 1]);
connect(free_before_alloc_array[0], free_reg);
#[hdl]
let expected_alloc = wire(Array[HdlOption[reg_num_ty]][alloc_at_once.get()]);

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,311 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
config::{CpuConfig, CpuConfig2PowOutRegNumWidth, CpuConfigUnitCount, PhantomConstCpuConfig},
instruction::{L2RegNum, MOpRegNum, PRegNum, UnitNum, UnitOutRegNum},
rename_execute_retire::L2RegFileLen,
};
use fayalite::{int::UIntInRangeInclusiveType, prelude::*};
#[hdl(no_static)]
pub(crate) struct RenameTableEntry<C: PhantomConstGet<CpuConfig>> {
pub(crate) l1: HdlOption<PRegNum<C>>,
pub(crate) l2: HdlOption<L2RegNum>,
}
impl<C: PhantomConstCpuConfig> RenameTableEntry<C> {
#[hdl]
pub(crate) fn const_zero(self) -> SimValue<Self> {
#[hdl(sim)]
Self {
l1: #[hdl(sim)]
(self.l1).HdlSome(self.l1.HdlSome.const_zero()),
l2: #[hdl(sim)]
HdlNone(),
}
}
}
/// make arrays dynamically-sized to avoid putting large types on the stack
#[hdl(get(|c| 1 << MOpRegNum::WIDTH))]
type MOpRegCount<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(no_static)]
pub(crate) struct RenameTableDebugState<C: PhantomConstGet<CpuConfig>> {
entries: ArrayType<TraceAsString<RenameTableEntry<C>>, MOpRegCount<C>>,
l1_ref_counts: ArrayType<
ArrayType<
UIntInRangeInclusiveType<ConstUsize<0>, MOpRegCount<C>>,
CpuConfig2PowOutRegNumWidth<C>,
>,
CpuConfigUnitCount<C>,
>,
l2_ref_counts:
ArrayType<UIntInRangeInclusiveType<ConstUsize<0>, MOpRegCount<C>>, L2RegFileLen<C>>,
config: C,
}
#[derive(Debug)]
pub(crate) struct RenameTable<C: PhantomConstCpuConfig> {
entries: Box<[SimValue<TraceAsString<RenameTableEntry<C>>>; 1 << MOpRegNum::WIDTH]>,
l1_ref_counts: Box<[Box<[usize]>]>,
l2_ref_counts: Box<[usize]>,
config: C,
}
impl<C: PhantomConstCpuConfig> Clone for RenameTable<C> {
fn clone(&self) -> Self {
Self {
entries: self.entries.clone(),
config: self.config.clone(),
l1_ref_counts: self.l1_ref_counts.clone(),
l2_ref_counts: self.l2_ref_counts.clone(),
}
}
fn clone_from(&mut self, source: &Self) {
let Self {
entries,
l1_ref_counts,
l2_ref_counts,
config,
} = self;
entries.clone_from(&source.entries);
l1_ref_counts.clone_from(&source.l1_ref_counts);
l2_ref_counts.clone_from(&source.l2_ref_counts);
*config = source.config;
}
}
#[derive(Debug, Clone)]
pub(crate) enum RenameTableUpdate<C: PhantomConstCpuConfig> {
Write {
unrenamed_reg_num: u32,
new: SimValue<TraceAsString<RenameTableEntry<C>>>,
},
UpdateForReadL2Reg {
dest: SimValue<PRegNum<C>>,
src: SimValue<L2RegNum>,
},
UpdateForWriteL2Reg {
dest: SimValue<L2RegNum>,
src: SimValue<PRegNum<C>>,
},
DropAllL2RegFileOutputs,
}
impl<C: PhantomConstCpuConfig> RenameTable<C> {
pub(crate) fn new(config: C) -> Self {
let entries: Box<[SimValue<TraceAsString<RenameTableEntry<C>>>; 1 << MOpRegNum::WIDTH]> =
vec![
RenameTableEntry[config].const_zero().into_trace_as_string();
1 << MOpRegNum::WIDTH
]
.try_into()
.expect("size is known to match");
Self {
entries,
l1_ref_counts: vec![
vec![0; CpuConfig2PowOutRegNumWidth[config]].into_boxed_slice();
CpuConfigUnitCount[config]
]
.into_boxed_slice(),
l2_ref_counts: vec![0; L2RegFileLen[config]].into_boxed_slice(),
config,
}
}
pub(crate) fn entries(
&self,
) -> &[SimValue<TraceAsString<RenameTableEntry<C>>>; 1 << MOpRegNum::WIDTH] {
&self.entries
}
#[hdl]
pub(crate) fn to_debug_state(&self) -> SimValue<RenameTableDebugState<C>> {
let Self {
entries,
l1_ref_counts,
l2_ref_counts,
config,
} = self;
let ty = RenameTableDebugState[*config];
#[hdl(sim)]
RenameTableDebugState::<_> {
entries: entries.to_sim_value_with_type(ty.entries),
l1_ref_counts: l1_ref_counts.to_sim_value_with_type(ty.l1_ref_counts),
l2_ref_counts: l2_ref_counts.to_sim_value_with_type(ty.l2_ref_counts),
config,
}
}
#[hdl]
pub(crate) fn l1_ref_counts(&self) -> &[Box<[usize]>] {
let mut expected = vec![
vec![0usize; CpuConfig2PowOutRegNumWidth[self.config]]
.into_boxed_slice();
CpuConfigUnitCount[self.config]
];
for entry in self.entries.iter() {
#[hdl(sim)]
let RenameTableEntry::<_> { l1, l2: _ } = entry.inner();
#[hdl(sim)]
if let HdlSome(l1) = l1 {
if let Some(unit_index) = UnitNum::index_sim(&l1.unit_num) {
expected[unit_index][UnitOutRegNum::value_sim(&l1.unit_out_reg)] += 1;
}
}
}
assert_eq!(*expected, *self.l1_ref_counts);
&self.l1_ref_counts
}
#[hdl]
pub(crate) fn l2_ref_counts(&self) -> &[usize] {
let mut expected = vec![0usize; L2RegNum.l2_reg_count()];
for entry in self.entries.iter() {
#[hdl(sim)]
let RenameTableEntry::<_> { l1: _, l2 } = entry.inner();
#[hdl(sim)]
if let HdlSome(l2) = l2 {
expected[L2RegNum::value_sim(l2)] += 1
}
}
assert_eq!(*expected, *self.l2_ref_counts);
&self.l2_ref_counts
}
#[hdl]
pub(crate) fn update<'a>(&mut self, update: &RenameTableUpdate<C>, rename_table_name: &str) {
let mut update_entry =
|entry: &mut SimValue<TraceAsString<RenameTableEntry<C>>>,
new: SimValue<TraceAsString<RenameTableEntry<C>>>| {
#[hdl(sim)]
let RenameTableEntry::<_> { l1, l2 } = entry.inner();
#[hdl(sim)]
if let HdlSome(l1) = l1 {
if let Some(unit_index) = UnitNum::index_sim(&l1.unit_num) {
let ref_count = &mut self.l1_ref_counts[unit_index]
[UnitOutRegNum::value_sim(&l1.unit_out_reg)];
*ref_count = ref_count.checked_sub(1).unwrap_or_else(|| {
unreachable!("{rename_table_name}: l1 ref count went negative: {l1:?}")
});
}
}
#[hdl(sim)]
if let HdlSome(l2) = l2 {
let ref_count = &mut self.l2_ref_counts[L2RegNum::value_sim(l2)];
*ref_count = ref_count.checked_sub(1).unwrap_or_else(|| {
unreachable!("{rename_table_name}: l2 ref count went negative: {l2:?}")
});
}
#[hdl(sim)]
let RenameTableEntry::<_> { l1, l2 } = new.inner();
#[hdl(sim)]
if let HdlSome(l1) = l1 {
if let Some(unit_index) = UnitNum::index_sim(&l1.unit_num) {
let ref_count = &mut self.l1_ref_counts[unit_index]
[UnitOutRegNum::value_sim(&l1.unit_out_reg)];
*ref_count += 1;
assert!(
*ref_count <= 1 << MOpRegNum::WIDTH,
"{rename_table_name}: l1 ref count overflowed: {l1:?}",
);
}
}
#[hdl(sim)]
if let HdlSome(l2) = l2 {
let ref_count = &mut self.l2_ref_counts[L2RegNum::value_sim(l2)];
*ref_count += 1;
assert!(
*ref_count <= 1 << MOpRegNum::WIDTH,
"{rename_table_name}: l2 ref count overflowed: {l2:?}",
);
}
*entry = new;
};
match update {
RenameTableUpdate::Write {
unrenamed_reg_num,
new,
} => {
if *unrenamed_reg_num == MOpRegNum::CONST_ZERO_REG_NUM {
// writing to const zero reg does nothing
return;
}
println!("{rename_table_name}: Write: {unrenamed_reg_num:#x} <- {new:?}");
update_entry(&mut self.entries[*unrenamed_reg_num as usize], new.clone());
}
RenameTableUpdate::UpdateForReadL2Reg { dest, src } => {
let new = #[hdl(sim)]
RenameTableEntry::<_> {
l1: #[hdl(sim)]
(HdlOption[dest.ty()]).HdlSome(dest),
l2: #[hdl(sim)]
HdlSome(src),
};
for (unrenamed_reg_num, entry) in self.entries.iter_mut().enumerate() {
#[hdl(sim)]
if let HdlSome(l2) = &entry.inner().l2 {
if L2RegNum::value_sim(l2) == L2RegNum::value_sim(src) {
println!(
"{rename_table_name}: UpdateForReadL2Reg: {unrenamed_reg_num:#x} \
updating from {entry:?} to {new:?}",
);
#[hdl(sim)]
if let HdlSome(_) = &entry.inner().l1 {
unreachable!("l1 should be HdlNone: {entry:?}");
}
update_entry(entry, new.to_trace_as_string());
}
}
}
}
RenameTableUpdate::UpdateForWriteL2Reg { dest, src } => {
let new = #[hdl(sim)]
RenameTableEntry::<_> {
l1: #[hdl(sim)]
(HdlOption[src.ty()]).HdlNone(),
l2: #[hdl(sim)]
HdlSome(dest),
};
for (unrenamed_reg_num, entry) in self.entries.iter_mut().enumerate() {
#[hdl(sim)]
if let HdlSome(l1) = &entry.inner().l1 {
if l1 == src {
println!(
"{rename_table_name}: UpdateForWriteL2Reg: {unrenamed_reg_num:#x} \
updating from {entry:?} to {new:?}",
);
#[hdl(sim)]
if let HdlSome(_) = &entry.inner().l2 {
unreachable!("l2 should be HdlNone: {entry:?}");
}
update_entry(entry, new.to_trace_as_string());
}
}
}
}
RenameTableUpdate::DropAllL2RegFileOutputs => {
for (unrenamed_reg_num, entry) in self.entries.iter_mut().enumerate() {
#[hdl(sim)]
if let HdlSome(_) = &entry.inner().l1 {
#[hdl(sim)]
if let HdlSome(_) = &entry.inner().l2 {
let mut new = entry.inner().clone();
new.l1 = #[hdl(sim)]
(new.l1.ty()).HdlNone();
println!(
"{rename_table_name}: DropAllL2RegFileOutputs: {unrenamed_reg_num:#x} \
updating from {entry:?} to {new:?}",
);
update_entry(entry, new.to_trace_as_string());
}
}
}
}
}
}
#[hdl]
pub(crate) fn used_unit_out_reg_count(&self, unit_index: usize) -> usize {
self.l1_ref_counts()[unit_index]
.iter()
.filter(|ref_count| **ref_count != 0)
.count()
}
}

View file

@ -1,564 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
config::{
CpuConfig, CpuConfig2PowOutRegNumWidth, CpuConfigRobSize, CpuConfigUnitCount,
PhantomConstCpuConfig,
},
instruction::{
COMMON_MOP_SRC_LEN, L2RegNum, L2RegisterFileMOp, MOp, MOpTrait, PRegNum, UnitNum,
UnitOutRegNum,
},
next_pc::SimValueDefault,
rename_execute_retire::{
L2RegFileLen, MOpId, MOpInUnitState, MOpInstance, NextPcPredictorOp, RenamedMOp,
UnitCausedCancel, rename_table::RenameTableUpdate, zeroed,
},
util::array_vec::ArrayVec,
};
use fayalite::{
int::{UIntInRangeInclusiveType, UIntInRangeType},
prelude::*,
ty::StaticType,
};
use std::{collections::VecDeque, mem};
#[hdl]
type SimOnlyMOpInUnitState = SimOnly<MOpInUnitState>;
#[hdl(no_static)]
struct RobEntryDebugState<C: PhantomConstGet<CpuConfig>> {
/// See [`RobEntry::is_register_fence`]
is_register_fence: Bool,
/// See [`RobEntry::is_register_fence`]
done_waiting_for_register_fences: Bool,
mop: MOpInstance<RenamedMOp<C>>,
unit_index: UIntInRangeType<ConstUsize<0>, CpuConfigUnitCount<C>>,
mop_in_unit_state: SimOnlyMOpInUnitState,
is_speculative: Bool,
all_prior_mops_finished_and_or_caused_cancel: Bool,
output: HdlOption<NextPcPredictorOp<C>>,
caused_cancel: HdlOption<UnitCausedCancel<C>>,
}
impl<C: PhantomConstCpuConfig> SimValueDefault for RobEntryDebugState<C> {
#[hdl]
fn sim_value_default(self) -> SimValue<Self> {
let Self {
mop,
is_register_fence: _,
done_waiting_for_register_fences: _,
unit_index,
mop_in_unit_state: _,
is_speculative: _,
all_prior_mops_finished_and_or_caused_cancel: _,
output,
caused_cancel,
} = self;
#[hdl(sim)]
Self {
mop: zeroed(mop),
is_register_fence: false,
done_waiting_for_register_fences: false,
unit_index: zeroed(unit_index),
mop_in_unit_state: SimOnlyValue::default(),
is_speculative: false,
all_prior_mops_finished_and_or_caused_cancel: false,
output: #[hdl(sim)]
output.HdlNone(),
caused_cancel: #[hdl(sim)]
caused_cancel.HdlNone(),
}
}
}
#[derive(Debug)]
pub(crate) struct RobEntry<C: PhantomConstCpuConfig> {
/// Block this and all later µOps until all prior µOps are finished and didn't cause a cancel.
/// Not to be confused with memory fences.
pub(crate) is_register_fence: bool,
/// See [`Self::is_register_fence`]
pub(crate) done_waiting_for_register_fences: bool,
pub(crate) mop: SimValue<MOpInstance<RenamedMOp<C>>>,
pub(crate) unit_index: usize,
pub(crate) mop_in_unit_state: MOpInUnitState,
pub(crate) is_speculative: bool,
pub(crate) all_prior_mops_finished_and_or_caused_cancel: bool,
pub(crate) output: Option<SimValue<NextPcPredictorOp<C>>>,
pub(crate) caused_cancel: Option<SimValue<UnitCausedCancel<C>>>,
}
impl<C: PhantomConstCpuConfig> RobEntry<C> {
pub(crate) fn new(
mop: SimValue<MOpInstance<RenamedMOp<C>>>,
unit_index: usize,
is_register_fence: bool,
) -> Self {
Self {
mop,
is_register_fence,
done_waiting_for_register_fences: false,
unit_index,
mop_in_unit_state: MOpInUnitState::NotYetEnqueued,
is_speculative: true,
all_prior_mops_finished_and_or_caused_cancel: false,
output: None,
caused_cancel: None,
}
}
fn dest_reg(&self) -> Option<&SimValue<PRegNum<C>>> {
let dest_reg = MOpTrait::dest_reg_sim_ref(self.mop.mop.inner());
let unit_index = UnitNum::index_sim(&dest_reg.unit_num)?;
assert_eq!(unit_index, self.unit_index);
Some(dest_reg)
}
fn unit_out_reg(&self) -> Option<&SimValue<UnitOutRegNum<C>>> {
Some(&self.dest_reg()?.unit_out_reg)
}
pub(crate) fn unit_out_reg_index(&self) -> Option<usize> {
Some(UnitOutRegNum::value_sim(self.unit_out_reg()?))
}
#[hdl]
fn debug_state(&self, config: C) -> SimValue<RobEntryDebugState<C>> {
let Self {
is_register_fence,
done_waiting_for_register_fences,
mop,
unit_index,
mop_in_unit_state,
is_speculative,
all_prior_mops_finished_and_or_caused_cancel,
output,
caused_cancel,
} = self;
let ret_ty = RobEntryDebugState[config];
#[hdl(sim)]
RobEntryDebugState::<C> {
is_register_fence,
done_waiting_for_register_fences,
mop,
unit_index: unit_index.into_sim_value_with_type(ret_ty.unit_index),
mop_in_unit_state: SimOnlyValue::new(*mop_in_unit_state),
is_speculative,
all_prior_mops_finished_and_or_caused_cancel,
output: output.into_sim_value_with_type(ret_ty.output),
caused_cancel: caused_cancel.into_sim_value_with_type(ret_ty.caused_cancel),
}
}
#[hdl]
fn for_each_reg<S>(
&self,
mut shared_state: S,
mut l1_reg: impl FnMut(&mut S, &SimValue<PRegNum<C>>),
mut l2_reg: impl FnMut(&mut S, &SimValue<L2RegNum>),
) {
l1_reg(
&mut shared_state,
MOpTrait::dest_reg_sim_ref(self.mop.mop.inner()),
);
MOpTrait::for_each_src_reg_sim_ref(self.mop.mop.inner(), &mut |l1, _| {
l1_reg(&mut shared_state, l1);
});
#[hdl(sim)]
if let RenamedMOp::<_>::TransformedMove(mop) = self.mop.mop.inner() {
#[hdl(sim)]
match mop {
L2RegisterFileMOp::<_, _>::ReadL2Reg(v) => {
l2_reg(&mut shared_state, &v.common.imm);
}
L2RegisterFileMOp::<_, _>::WriteL2Reg(v) => {
l2_reg(&mut shared_state, &v.common.imm);
}
}
}
}
}
#[hdl]
struct RobEntriesDebugState {
unrenamed: MOpInstance<MOp>,
/// number of renamed &micro;Ops that this unrenamed &micro;Op corresponds to
renamed_entries_len: UInt<8>,
}
impl SimValueDefault for RobEntriesDebugState {
#[hdl]
fn sim_value_default(self) -> SimValue<Self> {
let Self {
unrenamed,
renamed_entries_len,
} = self;
#[hdl(sim)]
Self {
unrenamed: zeroed(unrenamed),
renamed_entries_len: renamed_entries_len.sim_value_default(),
}
}
}
#[derive(Debug)]
struct RobEntries<C: PhantomConstCpuConfig> {
unrenamed: SimValue<MOpInstance<MOp>>,
rename_table_updates: Vec<RenameTableUpdate<C>>,
renamed_entries: VecDeque<RobEntry<C>>,
}
impl<C: PhantomConstCpuConfig> RobEntries<C> {
#[hdl]
fn debug_state(&self) -> SimValue<RobEntriesDebugState> {
let Self {
unrenamed,
rename_table_updates: _,
renamed_entries,
} = self;
#[hdl(sim)]
RobEntriesDebugState {
unrenamed,
renamed_entries_len: u8::try_from(renamed_entries.len())
.expect("renamed_entries.len() should fit in u8"),
}
}
}
#[hdl(get(|c| c.rob_size.get() * (1 + COMMON_MOP_SRC_LEN)))]
type L1RegMaxRefCount<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(no_static)]
pub(crate) struct ReorderBufferDebugState<C: PhantomConstGet<CpuConfig>> {
next_renamed_mop_id: MOpId,
entries: ArrayVec<RobEntriesDebugState, CpuConfigRobSize<C>>,
incomplete_back_entry: HdlOption<RobEntriesDebugState>,
renamed: ArrayVec<RobEntryDebugState<C>, CpuConfigRobSize<C>>,
l1_reg_ref_counts: ArrayType<
ArrayType<
UIntInRangeInclusiveType<ConstUsize<0>, L1RegMaxRefCount<C>>,
CpuConfig2PowOutRegNumWidth<C>,
>,
CpuConfigUnitCount<C>,
>,
l2_reg_ref_counts:
ArrayType<UIntInRangeInclusiveType<ConstUsize<0>, CpuConfigRobSize<C>>, L2RegFileLen<C>>,
config: C,
}
impl<C: PhantomConstCpuConfig> SimValueDefault for ReorderBufferDebugState<C> {
#[hdl]
fn sim_value_default(self) -> SimValue<Self> {
let Self {
next_renamed_mop_id,
entries,
incomplete_back_entry,
renamed,
l1_reg_ref_counts,
l2_reg_ref_counts,
config,
} = self;
#[hdl(sim)]
Self {
next_renamed_mop_id: next_renamed_mop_id.sim_value_default(),
entries: entries.sim_value_default(),
incomplete_back_entry: incomplete_back_entry.sim_value_default(),
renamed: renamed.sim_value_default(),
l1_reg_ref_counts: zeroed(l1_reg_ref_counts),
l2_reg_ref_counts: zeroed(l2_reg_ref_counts),
config,
}
}
}
#[derive(Debug)]
pub(crate) struct ReorderBuffer<C: PhantomConstCpuConfig> {
next_renamed_mop_id: SimValue<MOpId>,
entries: VecDeque<RobEntries<C>>,
incomplete_back_entry: Option<RobEntries<C>>,
l1_reg_ref_counts: Box<[Box<[usize]>]>,
l2_reg_ref_counts: Box<[usize]>,
config: C,
}
impl<C: PhantomConstCpuConfig> ReorderBuffer<C> {
pub(crate) fn new(config: C) -> Self {
Self {
next_renamed_mop_id: MOpId.zero().into_sim_value(),
entries: VecDeque::new(),
incomplete_back_entry: None,
l1_reg_ref_counts: vec![
vec![0; CpuConfig2PowOutRegNumWidth[config]].into_boxed_slice();
CpuConfigUnitCount[config]
]
.into_boxed_slice(),
l2_reg_ref_counts: vec![0; L2RegNum.l2_reg_count()].into_boxed_slice(),
config,
}
}
#[hdl]
pub(crate) fn l1_reg_ref_counts(&self) -> &[Box<[usize]>] {
let mut expected = vec![
vec![0usize; CpuConfig2PowOutRegNumWidth[self.config]]
.into_boxed_slice();
CpuConfigUnitCount[self.config]
];
for entry in self.renamed() {
entry.for_each_reg(
(),
|(), l1| {
if let Some(unit_index) = UnitNum::index_sim(&l1.unit_num) {
expected[unit_index][UnitOutRegNum::value_sim(&l1.unit_out_reg)] += 1
}
},
|(), _l2| {},
);
}
assert_eq!(*expected, *self.l1_reg_ref_counts);
&self.l1_reg_ref_counts
}
#[hdl]
pub(crate) fn l2_reg_ref_counts(&self) -> &[usize] {
let mut expected = vec![0usize; L2RegNum.l2_reg_count()];
for entry in self.renamed() {
entry.for_each_reg(
(),
|(), _l1| {},
|(), l2| expected[L2RegNum::value_sim(l2)] += 1,
);
}
assert_eq!(*expected, *self.l2_reg_ref_counts);
&self.l2_reg_ref_counts
}
#[hdl]
pub(crate) fn debug_state(&self) -> SimValue<ReorderBufferDebugState<C>> {
let Self {
next_renamed_mop_id,
entries,
incomplete_back_entry,
l1_reg_ref_counts,
l2_reg_ref_counts,
config,
} = self;
let ty = ReorderBufferDebugState[*config];
#[hdl(sim)]
ReorderBufferDebugState::<_> {
next_renamed_mop_id,
entries: ty
.entries
.from_iter_sim(
zeroed(StaticType::TYPE),
entries.iter().map(RobEntries::debug_state),
)
.expect("known to fit"),
incomplete_back_entry: incomplete_back_entry.as_ref().map(|v| v.debug_state()),
renamed: ty
.renamed
.from_iter_sim(
ty.renamed.element().sim_value_default(),
self.renamed().map(|v| v.debug_state(*config)),
)
.ok()
.expect("known to fit"),
l1_reg_ref_counts: l1_reg_ref_counts.to_sim_value_with_type(ty.l1_reg_ref_counts),
l2_reg_ref_counts: l2_reg_ref_counts.to_sim_value_with_type(ty.l2_reg_ref_counts),
config,
}
}
pub(crate) fn unrenamed_len(&self) -> usize {
self.entries.len()
}
pub(crate) fn unrenamed(
&self,
) -> impl DoubleEndedIterator<Item = &SimValue<MOpInstance<MOp>>> + Clone {
self.entries.iter().map(|v| &v.unrenamed)
}
fn retire_groups_unrenamed_ranges(
&self,
) -> impl Clone + Iterator<Item = std::ops::Range<usize>> {
let mut next_group_start = 0;
self.entries
.iter()
.enumerate()
.filter_map(move |(index, entry)| {
if *entry.unrenamed.is_last_mop_in_insn {
let group_start = next_group_start;
next_group_start = index + 1;
Some(group_start..next_group_start)
} else {
None
}
})
}
pub(crate) fn retire_groups(
&self,
) -> impl Clone + Iterator<Item: DoubleEndedIterator<Item = &VecDeque<RobEntry<C>>> + Clone>
{
self.retire_groups_unrenamed_ranges().map(|range| {
self.entries
.range(range)
.map(|entries| &entries.renamed_entries)
})
}
pub(crate) fn renamed_len(&self) -> usize {
let Self {
next_renamed_mop_id: _,
entries,
incomplete_back_entry,
l1_reg_ref_counts: _,
l2_reg_ref_counts: _,
config: _,
} = self;
entries
.iter()
.chain(incomplete_back_entry)
.map(|entries| entries.renamed_entries.len())
.sum()
}
pub(crate) fn renamed(&self) -> impl DoubleEndedIterator<Item = &RobEntry<C>> + Clone {
let Self {
next_renamed_mop_id: _,
entries,
incomplete_back_entry,
l1_reg_ref_counts: _,
l2_reg_ref_counts: _,
config: _,
} = self;
entries
.iter()
.chain(incomplete_back_entry)
.flat_map(|entries| &entries.renamed_entries)
}
pub(crate) fn renamed_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut RobEntry<C>> {
let Self {
next_renamed_mop_id: _,
entries,
incomplete_back_entry,
l1_reg_ref_counts: _,
l2_reg_ref_counts: _,
config: _,
} = self;
entries
.iter_mut()
.chain(incomplete_back_entry)
.flat_map(|entries| &mut entries.renamed_entries)
}
fn try_renamed_by_id_mut(&mut self, id: &SimValue<MOpId>) -> Option<&mut RobEntry<C>> {
self.renamed_mut().find(|v| v.mop.id == *id)
}
pub(crate) fn renamed_by_id_mut(&mut self, id: &SimValue<MOpId>) -> &mut RobEntry<C> {
match self.try_renamed_by_id_mut(id) {
Some(v) => v,
None => panic!("MOpId not found: {id:?}"),
}
}
pub(crate) fn renamed_push_back_with_new_id(
&mut self,
unrenamed: &SimValue<MOpInstance<MOp>>,
mut renamed: RobEntry<C>,
) -> &SimValue<MOpId> {
let replacement_id = self
.next_renamed_mop_id
.as_int()
.wrapping_add(1)
.into_sim_value();
renamed.mop.id = mem::replace(&mut self.next_renamed_mop_id, replacement_id);
println!("renamed_push_back_with_new_id: {:?}", renamed.mop);
renamed.for_each_reg(
(),
|(), l1| {
if let Some(unit_index) = UnitNum::index_sim(&l1.unit_num) {
self.l1_reg_ref_counts[unit_index]
[UnitOutRegNum::value_sim(&l1.unit_out_reg)] += 1;
}
},
|(), l2| self.l2_reg_ref_counts[L2RegNum::value_sim(l2)] += 1,
);
let renamed_entries = &mut self
.incomplete_back_entry
.get_or_insert_with(|| RobEntries {
unrenamed: unrenamed.clone(),
rename_table_updates: Vec::new(),
renamed_entries: VecDeque::new(),
})
.renamed_entries;
renamed_entries.push_back(renamed);
&renamed_entries.back().expect("just pushed").mop.id
}
pub(crate) fn has_incomplete_back_entry(&self) -> bool {
self.incomplete_back_entry.is_some()
}
pub(crate) fn finished_unrenamed_push_back(&mut self, unrenamed: &SimValue<MOpInstance<MOp>>) {
let entry = self
.incomplete_back_entry
.take()
.unwrap_or_else(|| RobEntries {
unrenamed: unrenamed.clone(),
rename_table_updates: Vec::new(),
renamed_entries: VecDeque::new(),
});
self.entries.push_back(entry);
}
pub(crate) fn clear(&mut self) {
let Self {
next_renamed_mop_id: _,
entries,
incomplete_back_entry,
l1_reg_ref_counts,
l2_reg_ref_counts,
config: _,
} = self;
entries.clear();
l2_reg_ref_counts.fill(0);
for i in l1_reg_ref_counts {
i.fill(0);
}
*incomplete_back_entry = None;
}
pub(crate) fn unrenamed_back_append_rename_table_update(
&mut self,
unrenamed: &SimValue<MOpInstance<MOp>>,
update: RenameTableUpdate<C>,
) {
self.incomplete_back_entry
.get_or_insert_with(|| RobEntries {
unrenamed: unrenamed.clone(),
rename_table_updates: Vec::new(),
renamed_entries: VecDeque::new(),
})
.rename_table_updates
.push(update);
}
pub(crate) fn all_mops_are_finished_and_or_caused_cancel(&self) -> bool {
self.renamed().next().is_none_or(|entry| {
entry.all_prior_mops_finished_and_or_caused_cancel
&& entry.mop_in_unit_state.is_finished_and_or_caused_cancel()
})
}
pub(crate) fn entries_pop_front(
&mut self,
) -> Option<(Vec<RenameTableUpdate<C>>, VecDeque<RobEntry<C>>)> {
let RobEntries {
unrenamed: _,
rename_table_updates,
renamed_entries,
} = self.entries.pop_front()?;
for entry in &renamed_entries {
entry.for_each_reg(
(),
|(), l1| {
if let Some(unit_index) = UnitNum::index_sim(&l1.unit_num) {
let ref_count = &mut self.l1_reg_ref_counts[unit_index]
[UnitOutRegNum::value_sim(&l1.unit_out_reg)];
*ref_count = ref_count.checked_sub(1).unwrap_or_else(|| {
unreachable!("ReorderBuffer: l1 ref count went negative: {l1:?}")
});
}
},
|(), l2| {
let ref_count = &mut self.l2_reg_ref_counts[L2RegNum::value_sim(l2)];
*ref_count = ref_count.checked_sub(1).unwrap_or_else(|| {
unreachable!("ReorderBuffer: l2 ref count went negative: {l2:?}")
});
},
);
}
Some((rename_table_updates, renamed_entries))
}
}

View file

@ -1,207 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{config::CpuConfig, rename_execute_retire::ExecuteToUnitInterface};
use fayalite::{
bundle::{BundleField, BundleType, NoBuilder},
expr::ops::FieldAccess,
intern::{Intern, Interned, Memoize},
prelude::*,
ty::{
OpaqueSimValue, OpaqueSimValueSlice, OpaqueSimValueWriter, OpaqueSimValueWritten,
SimValueDebug,
},
};
use std::{fmt, marker::PhantomData, ops::Index};
/// a bundle with fields of type [`ExecuteToUnitInterface<C>`] for each unit
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExecuteToUnitInterfaces<C: Type + PhantomConstGet<CpuConfig>> {
bundle_ty: Bundle,
config: C,
}
impl<C: Type + PhantomConstGet<CpuConfig>> fmt::Debug for ExecuteToUnitInterfaces<C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("ExecuteToUnitInterfaces ")?;
let fields = self.bundle_ty.fields();
let field_offsets = self.bundle_ty.field_offsets();
f.debug_set()
.entries(
fields.iter().enumerate().map(|(index, field)| {
field.fmt_debug_in_struct(field_offsets[index].bit_width)
}),
)
.finish()
}
}
const CONFIG_FIELD_NAME: &str = "config";
impl<C: Type + PhantomConstGet<CpuConfig>> ExecuteToUnitInterfaces<C> {
pub fn new(config: C) -> Self {
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
struct MyMemoize<C>(PhantomData<C>);
impl<C: Type + PhantomConstGet<CpuConfig>> Memoize for MyMemoize<C> {
type Input = C;
type InputOwned = C;
type Output = ExecuteToUnitInterfaces<C>;
fn inner(self, &config: &Self::Input) -> Self::Output {
let unit_ty = ExecuteToUnitInterface[config].canonical();
let bundle_ty = Bundle::new(
(0..config.get().units.len())
.map(|unit_index| BundleField {
name: format!(
"u{unit_index}_{:?}",
config.get().units[unit_index].kind,
)
.intern_deref(),
flipped: false,
ty: unit_ty,
})
.chain([BundleField {
name: CONFIG_FIELD_NAME.intern(),
flipped: false,
ty: config.canonical(),
}])
.collect(),
);
ExecuteToUnitInterfaces { bundle_ty, config }
}
}
MyMemoize(PhantomData).get_owned(config)
}
pub fn bundle_ty(self) -> Bundle {
self.bundle_ty
}
pub fn config(self) -> C {
self.config
}
#[track_caller]
pub fn unit_field_name(self, unit_index: usize) -> Interned<str> {
assert!(unit_index < self.config.get().units.len());
self.bundle_ty.fields()[unit_index].name
}
pub fn unit_fields(
this: impl ToExpr<Type = Self>,
) -> Interned<[Expr<ExecuteToUnitInterface<C>>]> {
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
struct MyMemoize<C>(PhantomData<C>);
impl<C: Type + PhantomConstGet<CpuConfig>> Memoize for MyMemoize<C> {
type Input = Expr<ExecuteToUnitInterfaces<C>>;
type InputOwned = Expr<ExecuteToUnitInterfaces<C>>;
type Output = Interned<[Expr<ExecuteToUnitInterface<C>>]>;
fn inner(self, this: &Self::Input) -> Self::Output {
let bundle = Expr::as_bundle(*this);
(0..this.ty().config.get().units.len())
.map(|unit_index| FieldAccess::new_by_index(bundle, unit_index).to_expr())
.collect()
}
}
MyMemoize(PhantomData).get_owned(this.to_expr())
}
}
#[doc(hidden)]
pub struct ExecuteToUnitInterfacesWithoutGenerics(());
#[expect(non_upper_case_globals)]
pub const ExecuteToUnitInterfaces: ExecuteToUnitInterfacesWithoutGenerics =
ExecuteToUnitInterfacesWithoutGenerics(());
impl<C: Type + PhantomConstGet<CpuConfig>> Index<C> for ExecuteToUnitInterfacesWithoutGenerics {
type Output = ExecuteToUnitInterfaces<C>;
fn index(&self, config: C) -> &Self::Output {
Interned::into_inner(ExecuteToUnitInterfaces::new(config).intern())
}
}
impl<C: Type + PhantomConstGet<CpuConfig>> SimValueDebug for ExecuteToUnitInterfaces<C> {
fn sim_value_debug(
value: &<Self as Type>::SimValue,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
fmt::Debug::fmt(value, f)
}
}
impl<C: Type + PhantomConstGet<CpuConfig>> Type for ExecuteToUnitInterfaces<C> {
type BaseType = Bundle;
type MaskType = Bundle;
type SimValue = OpaqueSimValue;
type MatchVariant = <Bundle as Type>::MatchVariant;
type MatchActiveScope = <Bundle as Type>::MatchActiveScope;
type MatchVariantAndInactiveScope = <Bundle as Type>::MatchVariantAndInactiveScope;
type MatchVariantsIter = <Bundle as Type>::MatchVariantsIter;
fn match_variants(
this: Expr<Self>,
source_location: SourceLocation,
) -> Self::MatchVariantsIter {
Type::match_variants(Expr::as_bundle(this), source_location)
}
fn mask_type(&self) -> Self::MaskType {
self.bundle_ty.mask_type()
}
fn canonical(&self) -> CanonicalType {
self.bundle_ty.canonical()
}
fn from_canonical(canonical_type: CanonicalType) -> Self {
let bundle_ty = Bundle::from_canonical(canonical_type);
let config = if let Some(BundleField {
name,
flipped: false,
ty,
}) = bundle_ty.fields().last()
&& **name == *CONFIG_FIELD_NAME
{
C::from_canonical(*ty)
} else {
panic!(
"ExecuteToUnitInterfaces must have `{}` field",
CONFIG_FIELD_NAME
);
};
let retval = Self::new(config);
assert_eq!(bundle_ty, retval.bundle_ty);
retval
}
fn source_location() -> SourceLocation {
SourceLocation::caller()
}
fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue {
<Bundle as Type>::sim_value_from_opaque(&self.bundle_ty, opaque)
}
fn sim_value_clone_from_opaque(
&self,
value: &mut Self::SimValue,
opaque: OpaqueSimValueSlice<'_>,
) {
<Bundle as Type>::sim_value_clone_from_opaque(&self.bundle_ty, value, opaque)
}
fn sim_value_to_opaque<'w>(
&self,
value: &Self::SimValue,
writer: OpaqueSimValueWriter<'w>,
) -> OpaqueSimValueWritten<'w> {
<Bundle as Type>::sim_value_to_opaque(&self.bundle_ty, value, writer)
}
}
impl<C: Type + PhantomConstGet<CpuConfig>> BundleType for ExecuteToUnitInterfaces<C> {
type Builder = NoBuilder;
fn fields(&self) -> Interned<[BundleField]> {
self.bundle_ty.fields()
}
}

View file

@ -1,41 +0,0 @@
%% SPDX-License-Identifier: LGPL-3.0-or-later
%% See Notices.txt for copyright information
stateDiagram-v2
direction LR
state "Inputs not ready<br/>Speculative<br/>Can cause cancel" as inr_s_c
state "Inputs not ready<br/>Speculative" as inr_s
state "Inputs ready<br/>Speculative<br/>Can cause cancel" as ir_s_c
state "Inputs ready<br/>Speculative" as ir_s
state "Inputs ready<br/>Can cause cancel" as ir_c
state "Inputs ready" as ir
state "Output ready<br/>Speculative<br/>Can cause cancel" as or_s_c
state "Output ready<br/>Can cause cancel" as or_c
state "Output ready<br/>Speculative" as or_s
state "Output ready" as or
[*] --> inr_s_c: Enqueue
inr_s_c --> inr_s: Can't cause cancel
ir_s_c --> ir_s: Can't cause cancel
ir_c --> ir: Can't cause cancel
or_s_c --> or_s: Can't cause cancel
or_c --> or: Can't cause cancel
ir_s_c --> ir_c: No longer speculative
ir_s --> ir: No longer speculative
or_s_c --> or_c: No longer speculative
or_s --> or: No longer speculative
inr_s_c --> ir_s_c: Inputs ready
inr_s --> ir_s: Inputs ready
ir_s_c --> or_s_c: Output Ready
ir_c --> or_c: Output Ready
ir_s --> or_s: Output Ready
ir --> or: Output Ready
or_s_c --> [*]: Finish
or_c --> [*]: Finish
or_s --> [*]: Finish
or --> [*]: Finish

View file

@ -0,0 +1,187 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
config::{CpuConfig, CpuConfigType},
instruction::{MOpRegNum, PRegNum},
util::range_intersection,
};
use fayalite::{
memory::{splat_mask, ReadStruct, WriteStruct},
module::memory_with_loc,
prelude::*,
};
use std::{mem, ops::Range};
#[hdl(no_static)]
pub struct RenameTableReadPort<C: Type + CpuConfigType> {
pub addr: MOpRegNum,
#[hdl(flip)]
pub data: PRegNum<C>,
}
#[hdl(no_static)]
pub struct RenameTableWritePort<C: Type + CpuConfigType> {
pub addr: MOpRegNum,
pub data: PRegNum<C>,
}
#[derive(Clone, Debug)]
pub enum RenameTablePortConfig {
Read { addr_range: Range<u32> },
Write { addr_range: Range<u32> },
}
type C = PhantomConst<CpuConfig>;
/// register rename table.
/// all read/write operations are done in the order of `port_configs`.
/// So if `port_configs[0]` is a write and `port_configs[1]` is a read,
/// then the read port will combinatorially return data written by the
/// write port in the *same* clock cycle. However, if `port_configs[0]`
/// is a read and `port_configs[1]` is a write, then the read port will
/// not see the data written by the write port until the *next* clock cycle.
#[hdl_module]
pub fn rename_table(config: PhantomConst<CpuConfig>, port_configs: &[RenameTablePortConfig]) {
let read_count = port_configs
.iter()
.filter(|v| matches!(v, RenameTablePortConfig::Read { .. }))
.count();
let write_count = port_configs
.iter()
.filter(|v| matches!(v, RenameTablePortConfig::Write { .. }))
.count();
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let read_ports: Array<RenameTableReadPort<C>> =
m.input(Array[RenameTableReadPort[config]][read_count]);
#[hdl]
let write_ports: Array<RenameTableWritePort<C>> =
m.input(Array[RenameTableWritePort[config]][write_count]);
for read_port in read_ports {
connect(read_port.data, PRegNum[config].const_zero());
}
let port_configs_and_indexes = port_configs.iter().scan(
(0usize, 0),
|(read_port_index, write_port_index), port_config| {
Some((
port_config,
match port_config {
RenameTablePortConfig::Read { .. } => {
mem::replace(read_port_index, *read_port_index + 1)
}
RenameTablePortConfig::Write { .. } => {
mem::replace(write_port_index, *write_port_index + 1)
}
},
))
},
);
let mut range_transitions = Vec::with_capacity(port_configs.len() * 2);
for port_config in port_configs {
let (RenameTablePortConfig::Read { addr_range }
| RenameTablePortConfig::Write { addr_range }) = port_config;
range_transitions.push(addr_range.start);
range_transitions.push(addr_range.end);
}
range_transitions.sort_unstable();
range_transitions.dedup();
let mut last_range_transition = None;
for range_transition in range_transitions {
let Some(last_range_transition) = last_range_transition.replace(range_transition) else {
continue;
};
let cur_addr_range = last_range_transition..range_transition;
let mut mem = memory_with_loc(
&if cur_addr_range.len() == 1 {
format!("mem_{:#x}", cur_addr_range.start)
} else {
format!("mem_{:#x}_{:#x}", cur_addr_range.start, cur_addr_range.end)
},
PRegNum[config],
SourceLocation::caller(),
);
mem.depth(cur_addr_range.len());
let addr_in_range = |addr: Expr<MOpRegNum>| {
if cur_addr_range.len() == 1 {
addr.value.cmp_eq(cur_addr_range.start)
} else {
addr.value.cmp_ge(cur_addr_range.start) & addr.value.cmp_lt(cur_addr_range.end)
}
};
for (port_config, port_index) in port_configs_and_indexes.clone() {
match port_config {
RenameTablePortConfig::Read { addr_range } => {
if range_intersection(&addr_range, &cur_addr_range).is_none() {
continue;
}
let port = read_ports[port_index];
#[hdl]
let ReadStruct::<_, _> {
addr,
en,
clk,
data,
} = mem.new_read_port();
connect_any(addr, port.addr.value - cur_addr_range.start);
connect(en, addr_in_range(port.addr));
connect(clk, cd.clk);
#[hdl]
if en {
connect(port.data, data);
}
}
RenameTablePortConfig::Write { addr_range } => {
if range_intersection(&addr_range, &cur_addr_range).is_none() {
continue;
}
let port = write_ports[port_index];
#[hdl]
let WriteStruct::<_, _> {
addr,
en,
clk,
data,
mask,
} = mem.new_write_port();
connect_any(addr, port.addr.value - cur_addr_range.start);
connect(en, addr_in_range(port.addr));
connect(clk, cd.clk);
connect(data, port.data);
connect(mask, splat_mask(Expr::ty(port).data, true.to_expr()));
}
}
}
}
for (port_config_index, (port_config, port_index)) in
port_configs_and_indexes.clone().enumerate()
{
let RenameTablePortConfig::Read { addr_range } = port_config else {
continue;
};
let port = read_ports[port_index];
for (prev_port_config, prev_port_index) in
port_configs_and_indexes.clone().take(port_config_index)
{
let RenameTablePortConfig::Write {
addr_range: prev_addr_range,
} = prev_port_config
else {
continue;
};
if range_intersection(addr_range, prev_addr_range).is_none() {
continue;
}
let prev_port = write_ports[prev_port_index];
#[hdl]
if prev_port.addr.cmp_eq(port.addr) {
connect(port.data, prev_port.data);
}
}
}
}

View file

@ -0,0 +1,33 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
config::{CpuConfig, CpuConfigType},
instruction::{MOpDestReg, PRegNum},
unit::RetireQueueIndex,
};
use fayalite::prelude::*;
#[hdl(no_static)]
pub struct RenameRetireInterface<C: Type + CpuConfigType> {
pub start_retire_queue_index: RetireQueueIndex<C>,
#[hdl(flip)]
pub end_retire_queue_index: RetireQueueIndex<C>,
}
#[hdl(no_static)]
pub struct RetireQueueEntry<C: Type + CpuConfigType> {
pub mop_dest: MOpDestReg,
pub renamed_dest: PRegNum<C>,
}
type C = PhantomConst<CpuConfig>;
#[hdl_module]
pub fn retire_queue(config: PhantomConst<CpuConfig>) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let rename_retire_interface: RenameRetireInterface<C> = m.output(RenameRetireInterface[config]);
todo!();
}

View file

@ -2,34 +2,36 @@
// See Notices.txt for copyright information
use crate::{
config::CpuConfig,
config::{CpuConfig, CpuConfigType, RetireQueueIndexWidth, UnitCount},
instruction::{
AluBranchMOp, LoadStoreMOp, MOp, MOpDestReg, MOpInto, MOpRegNum, MOpTrait,
MOpVariantVisitOps, MOpVariantVisitor, MOpVisitVariants, RenamedMOp, mop_enum,
mop_enum, AluBranchMOp, LoadStoreMOp, MOp, MOpDestReg, MOpInto, MOpRegNum, MOpTrait,
RenamedMOp, UnitOutRegNum,
},
rename_execute_retire::ExecuteToUnitInterface,
register::{FlagsMode, PRegValue},
};
use fayalite::{
bundle::{Bundle, BundleType},
int::BoolOrIntType,
intern::{Intern, Interned},
prelude::*,
util::ready_valid::ReadyValid,
};
use serde::{Deserialize, Serialize};
use std::ops::ControlFlow;
pub mod alu_branch;
pub mod unit_base;
macro_rules! all_units {
(
#[hdl_unit_kind = $HdlUnitKind:ident]
#[unit_kind = $UnitKind:ident]
#[hdl(custom_debug(sim))]
#[hdl]
$(#[$enum_meta:meta])*
$vis:vis enum $UnitMOpEnum:ident<$DestReg:ident: Type, $SrcReg:ident: Type, #[MOp(get_ty = $transformed_move_op_get_ty:expr)] $TransformedMoveOp:ident: Type> {
$vis:vis enum $UnitMOpEnum:ident<$DestReg:ident: Type, $SrcRegWidth:ident: Size, #[MOp(get_ty = $transformed_move_op_get_ty:expr)] $TransformedMoveOp:ident: Type> {
$(
$(#[transformed_move $($transformed_move:tt)*])?
#[create_dyn_unit_fn = $create_dyn_unit_fn:expr]
#[extract($extract:ident, $extract_sim:ident, $extract_sim_ref:ident, $extract_sim_mut:ident)]
#[extract = $extract:ident]
$(#[$variant_meta:meta])*
$Unit:ident($Op:ty),
)*
@ -52,16 +54,9 @@ macro_rules! all_units {
}
}
impl ValueType for $UnitKind {
type Type = $HdlUnitKind;
type ValueCategory = fayalite::expr::value_category::ValueCategoryExpr;
fn ty(&self) -> Self::Type {
$HdlUnitKind
}
}
impl ToExpr for $UnitKind {
type Type = $HdlUnitKind;
fn to_expr(&self) -> Expr<Self::Type> {
match self {
$($UnitKind::$Unit => $HdlUnitKind.$Unit(),)*
@ -80,17 +75,9 @@ macro_rules! all_units {
mop_enum! {
#[impl_mop_into = false]
#[hdl(custom_debug(sim))]
#[hdl]
$(#[$enum_meta])*
$vis enum $UnitMOpEnum<
$DestReg: Type,
$SrcReg: Type,
#[MOp(get_ty = $transformed_move_op_get_ty)] $TransformedMoveOp: Type,
#[MOpVisitVariants] [
$TransformedMoveOp: MOpVisitVariants<DestReg = $DestReg, SrcReg = $SrcReg>,
$($Op: MOpVisitVariants<DestReg = $DestReg, SrcReg = $SrcReg>,)*
]
> {
$vis enum $UnitMOpEnum<$DestReg: Type, $SrcRegWidth: Size, #[MOp(get_ty = $transformed_move_op_get_ty)] $TransformedMoveOp: Type> {
$(
$(#[$variant_meta])*
$Unit($Op),
@ -98,7 +85,7 @@ macro_rules! all_units {
}
}
impl<$DestReg: Type, $SrcReg: Type, $TransformedMoveOp: Type> $UnitMOpEnum<$DestReg, $SrcReg, $TransformedMoveOp> {
impl<$DestReg: Type, $SrcRegWidth: Size, $TransformedMoveOp: Type> $UnitMOpEnum<$DestReg, $SrcRegWidth, $TransformedMoveOp> {
#[hdl]
$vis fn kind(expr: impl ToExpr<Type = Self>) -> Expr<$HdlUnitKind> {
#[hdl]
@ -109,20 +96,11 @@ macro_rules! all_units {
}
unit_kind
}
#[hdl]
$vis fn kind_sim(expr: &SimValue<Self>) -> UnitKind {
#![allow(unreachable_patterns)]
#[hdl(sim)]
match expr {
$(Self::$Unit(_) => $UnitKind::$Unit,)*
_ => unreachable!(),
}
}
$(
#[hdl]
$vis fn $extract(expr: impl ToExpr<Type = Self>) -> Expr<HdlOption<$Op>> {
let expr = expr.to_expr();
let ty = expr.ty();
let ty = Expr::ty(expr);
#[hdl]
let $extract = wire(HdlOption[ty.$Unit]);
connect($extract, HdlOption[ty.$Unit].HdlNone());
@ -132,47 +110,19 @@ macro_rules! all_units {
}
$extract
}
#[hdl]
$vis fn $extract_sim(expr: impl ToSimValue<Type = Self>) -> Option<SimValue<$Op>> {
let expr = expr.into_sim_value();
#[hdl(sim)]
if let Self::$Unit(v) = expr {
Some(v)
} else {
None
}
}
#[hdl]
$vis fn $extract_sim_ref(expr: &SimValue<Self>) -> Option<&SimValue<$Op>> {
#[hdl(sim)]
if let Self::$Unit(v) = expr {
Some(v)
} else {
None
}
}
#[hdl]
$vis fn $extract_sim_mut(expr: &mut SimValue<Self>) -> Option<&mut SimValue<$Op>> {
#[hdl(sim)]
if let Self::$Unit(v) = expr {
Some(v)
} else {
None
}
}
)*
$vis fn with_transformed_move_op_ty<T>(self, new_transformed_move_op_ty: T) -> $UnitMOpEnum<$DestReg, $SrcReg, T>
$vis fn with_transformed_move_op_ty<T>(self, new_transformed_move_op_ty: T) -> $UnitMOpEnum<$DestReg, $SrcRegWidth, T>
where
T: MOpTrait<DestReg = $DestReg, SrcReg = $SrcReg>,
$TransformedMoveOp: MOpTrait<DestReg = $DestReg, SrcReg = $SrcReg>,
T: MOpTrait<DestReg = $DestReg, SrcRegWidth = $SrcRegWidth>,
$TransformedMoveOp: MOpTrait<DestReg = $DestReg, SrcRegWidth = $SrcRegWidth>,
{
$UnitMOpEnum[self.dest_reg_ty()][self.src_reg_ty()][new_transformed_move_op_ty]
$UnitMOpEnum[self.dest_reg_ty()][self.src_reg_width()][new_transformed_move_op_ty]
}
}
all_units! {
@split_by_transformed_move
$vis enum $UnitMOpEnum<$DestReg: Type, $SrcReg: Type, $TransformedMoveOp: Type> {
$vis enum $UnitMOpEnum<$DestReg: Type, $SrcRegWidth: Size, $TransformedMoveOp: Type> {
$(
$(#[transformed_move $($transformed_move)*])?
$Unit($Op),
@ -197,29 +147,29 @@ macro_rules! all_units {
};
(
@split_by_transformed_move
$vis:vis enum $UnitMOpEnum:ident<$DestReg:ident: Type, $SrcReg:ident: Type, $TransformedMoveOp:ident: Type> {
$vis:vis enum $UnitMOpEnum:ident<$DestReg:ident: Type, $SrcRegWidth:ident: Size, $TransformedMoveOp:ident: Type> {
$($BeforeUnit:ident($BeforeOp:ty),)*
#[transformed_move]
$TransformedMove:ident($TransformedMoveOp2:ty),
$($AfterUnit:ident($AfterOp:ty),)*
}
) => {
impl<$DestReg: Type, $SrcReg: Type, $TransformedMoveOp: Type> $UnitMOpEnum<$DestReg, $SrcReg, $TransformedMoveOp> {
impl<$DestReg: Type, $SrcRegWidth: Size, $TransformedMoveOp: Type> $UnitMOpEnum<$DestReg, $SrcRegWidth, $TransformedMoveOp> {
#[hdl]
$vis fn try_with_transformed_move_op<T>(
this: impl ToExpr<Type = Self>,
new_transformed_move_op_ty: T,
connect_transformed_move_op: impl FnOnce(Expr<HdlOption<$UnitMOpEnum<$DestReg, $SrcReg, T>>>, Expr<$TransformedMoveOp>),
) -> Expr<HdlOption<$UnitMOpEnum<$DestReg, $SrcReg, T>>>
connect_transformed_move_op: impl FnOnce(Expr<HdlOption<$UnitMOpEnum<$DestReg, $SrcRegWidth, T>>>, Expr<$TransformedMoveOp>),
) -> Expr<HdlOption<$UnitMOpEnum<$DestReg, $SrcRegWidth, T>>>
where
T: MOpTrait<DestReg = $DestReg, SrcReg = $SrcReg>,
$TransformedMoveOp: MOpTrait<DestReg = $DestReg, SrcReg = $SrcReg>,
T: MOpTrait<DestReg = $DestReg, SrcRegWidth = $SrcRegWidth>,
$TransformedMoveOp: MOpTrait<DestReg = $DestReg, SrcRegWidth = $SrcRegWidth>,
{
let this = this.to_expr();
let new_ty = this.ty().with_transformed_move_op_ty(new_transformed_move_op_ty);
let new_ty = Expr::ty(this).with_transformed_move_op_ty(new_transformed_move_op_ty);
#[hdl]
let with_transformed_move_op = wire(HdlOption[new_ty]);
connect(with_transformed_move_op, with_transformed_move_op.ty().HdlNone());
connect(with_transformed_move_op, Expr::ty(with_transformed_move_op).HdlNone());
// workaround #[hdl] match expanding to a loop, so you can't move variables in it
let mut connect_transformed_move_op = Some(connect_transformed_move_op);
#[hdl]
@ -230,51 +180,12 @@ macro_rules! all_units {
}
with_transformed_move_op
}
#[hdl]
$vis fn try_with_transformed_move_op_sim<T, E>(
this: impl ToSimValue<Type = Self>,
new_transformed_move_op_ty: T,
f: impl FnOnce(SimValue<$TransformedMoveOp>) -> Result<SimValue<$UnitMOpEnum<$DestReg, $SrcReg, T>>, E>,
) -> Result<SimValue<$UnitMOpEnum<$DestReg, $SrcReg, T>>, E>
where
T: MOpTrait<DestReg = $DestReg, SrcReg = $SrcReg>,
$TransformedMoveOp: MOpTrait<DestReg = $DestReg, SrcReg = $SrcReg>,
{
#![allow(unreachable_patterns)]
let this = this.into_sim_value();
let new_ty = this.ty().with_transformed_move_op_ty(new_transformed_move_op_ty);
#[hdl(sim)]
match this {
$(Self::$BeforeUnit(unit) => Ok(
#[hdl(sim)]
new_ty.$BeforeUnit(unit)
),)*
Self::$TransformedMove(unit) => f(unit),
$(Self::$AfterUnit(unit) => Ok(
#[hdl(sim)]
new_ty.$AfterUnit(unit)
),)*
_ => unreachable!(),
}
}
$vis fn with_transformed_move_op_sim<T>(
this: impl ToSimValue<Type = Self>,
new_transformed_move_op_ty: T,
f: impl FnOnce(SimValue<$TransformedMoveOp>) -> SimValue<$UnitMOpEnum<$DestReg, $SrcReg, T>>,
) -> SimValue<$UnitMOpEnum<$DestReg, $SrcReg, T>>
where
T: MOpTrait<DestReg = $DestReg, SrcReg = $SrcReg>,
$TransformedMoveOp: MOpTrait<DestReg = $DestReg, SrcReg = $SrcReg>,
{
let Ok::<_, std::convert::Infallible>(retval) = Self::try_with_transformed_move_op_sim(this, new_transformed_move_op_ty, move |v| Ok(f(v)));
retval
}
}
const _: () = {
#[hdl]
type $DestReg = MOpDestReg;
type $SrcReg = MOpRegNum;
type $SrcRegWidth = ConstUsize<{ MOpRegNum::WIDTH }>;
$(impl MOpInto<MOp> for $BeforeOp {
fn mop_into_ty(self) -> MOp {
@ -295,47 +206,175 @@ macro_rules! all_units {
})*
};
$(impl<$DestReg: Type, $SrcReg: Type> MOpInto<RenamedMOp<$DestReg, $SrcReg>> for $BeforeOp {
fn mop_into_ty(self) -> RenamedMOp<$DestReg, $SrcReg> {
RenamedMOp[MOpTrait::dest_reg_ty(self)][MOpTrait::src_reg_ty(self)]
}
fn mop_into(this: Expr<Self>) -> Expr<RenamedMOp<$DestReg, $SrcReg>> {
MOpInto::<RenamedMOp<$DestReg, $SrcReg>>::mop_into_ty(this.ty()).$BeforeUnit(this)
}
})*
const _: () = {
#[hdl]
type $DestReg = ();
$(impl<$DestReg: Type, $SrcReg: Type> MOpInto<RenamedMOp<$DestReg, $SrcReg>> for $AfterOp {
fn mop_into_ty(self) -> RenamedMOp<$DestReg, $SrcReg> {
RenamedMOp[MOpTrait::dest_reg_ty(self)][MOpTrait::src_reg_ty(self)]
}
fn mop_into(this: Expr<Self>) -> Expr<RenamedMOp<$DestReg, $SrcReg>> {
MOpInto::<RenamedMOp<$DestReg, $SrcReg>>::mop_into_ty(this.ty()).$AfterUnit(this)
}
})*
$(impl<$SrcRegWidth: Size> MOpInto<RenamedMOp<$SrcRegWidth>> for $BeforeOp {
fn mop_into_ty(self) -> RenamedMOp<$SrcRegWidth> {
RenamedMOp[MOpTrait::src_reg_width(self)]
}
fn mop_into(this: Expr<Self>) -> Expr<RenamedMOp<$SrcRegWidth>> {
MOpInto::<RenamedMOp<$SrcRegWidth>>::mop_into_ty(Expr::ty(this)).$BeforeUnit(this)
}
})*
$(impl<$SrcRegWidth: Size> MOpInto<RenamedMOp<$SrcRegWidth>> for $AfterOp {
fn mop_into_ty(self) -> RenamedMOp<$SrcRegWidth> {
RenamedMOp[MOpTrait::src_reg_width(self)]
}
fn mop_into(this: Expr<Self>) -> Expr<RenamedMOp<$SrcRegWidth>> {
MOpInto::<RenamedMOp<$SrcRegWidth>>::mop_into_ty(Expr::ty(this)).$AfterUnit(this)
}
})*
};
};
}
all_units! {
#[hdl_unit_kind = HdlUnitKind]
#[unit_kind = UnitKind]
#[hdl(custom_debug(sim))]
#[hdl]
pub enum UnitMOp<
DestReg: Type,
SrcReg: Type,
#[MOp(get_ty = |this: UnitMOp<DestReg, SrcReg, TransformedMoveOp>, new_dest_reg, new_src_reg| {
this.TransformedMove.mapped_ty(new_dest_reg, new_src_reg)
SrcRegWidth: Size,
#[MOp(get_ty = |this: UnitMOp<DestReg, SrcRegWidth, TransformedMoveOp>, new_dest_reg, new_src_reg_width| {
this.TransformedMove.mapped_ty(new_dest_reg, new_src_reg_width)
})] TransformedMoveOp: Type
> {
#[create_dyn_unit_fn = |config, unit_index| alu_branch::AluBranch::new(config, unit_index).to_dyn()]
#[extract(alu_branch_mop, alu_branch_mop_sim, alu_branch_mop_sim_ref, alu_branch_mop_sim_mut)]
AluBranch(AluBranchMOp<DestReg, SrcReg>),
#[extract = alu_branch_mop]
AluBranch(AluBranchMOp<DestReg, SrcRegWidth>),
#[transformed_move]
#[create_dyn_unit_fn = |config, unit_index| todo!()]
#[extract(transformed_move_mop, transformed_move_mop_sim, transformed_move_mop_sim_ref, transformed_move_mop_sim_mut)]
#[extract = transformed_move_mop]
TransformedMove(TransformedMoveOp),
#[create_dyn_unit_fn = |config, unit_index| todo!()]
#[extract(load_store_mop, load_store_mop_sim, load_store_mop_sim_ref, load_store_mop_sim_mut)]
LoadStore(LoadStoreMOp<DestReg, SrcReg>),
#[extract = load_store_mop]
LoadStore(LoadStoreMOp<DestReg, SrcRegWidth>),
}
}
#[hdl]
pub struct GlobalState {
pub flags_mode: FlagsMode,
}
/// index into the retire queue (the queue of instructions that haven't yet retired)
#[hdl(cmp_eq, no_static)]
pub struct RetireQueueIndex<C: Type + CpuConfigType> {
/// increases by one for each instruction added to the retire queue.
///
/// this wraps around, so you must not compare it using `cmp_lt`/`cmp_gt`
/// but instead must use [`Self::insns_until`] and compare the output with zero.
pub index: UIntType<RetireQueueIndexWidth<C>>,
pub config: C,
}
impl<C: Type + CpuConfigType> RetireQueueIndex<C> {
pub fn insns_until(
this: impl ToExpr<Type = Self>,
target: impl ToExpr<Type = Self>,
) -> Expr<SIntType<RetireQueueIndexWidth<C>>> {
let this = this.to_expr();
let target = target.to_expr();
assert_eq!(Expr::ty(this), Expr::ty(target));
(this.index - target.index).cast_to(Expr::ty(this).index.as_same_width_sint())
}
}
#[hdl(no_static)]
pub struct RenamedInsnData<C: Type + CpuConfigType, MOp, DestReg> {
pub retire_queue_index: RetireQueueIndex<C>,
pub pc: UInt<64>,
pub dest: DestReg,
pub mop: MOp,
}
#[hdl(no_static)]
pub struct UnitForwardingInfo<C: Type + CpuConfigType> {
pub unit_output_writes: ArrayType<HdlOption<UnitOutputWrite<C>>, UnitCount<C>>,
pub unit_reg_frees: ArrayType<HdlOption<UnitOutRegNum<C>>, UnitCount<C>>,
}
#[hdl(no_static)]
pub struct UnitToRegAlloc<C: Type + CpuConfigType, MOp: Type, ExtraOut: Type> {
#[hdl(flip)]
pub unit_forwarding_info: UnitForwardingInfo<C>,
#[hdl(flip)]
pub input: ReadyValid<RenamedInsnData<C, MOp, UnitOutRegNum<C>>>,
#[hdl(flip)]
pub cancel_input: HdlOption<UnitCancelInput<C>>,
pub output: HdlOption<UnitOutput<C, ExtraOut>>,
pub ready_for_retire_queue_index: HdlOption<RetireQueueIndex<C>>,
}
impl<C: Type + CpuConfigType, MOp: Type, ExtraOut: Type> UnitToRegAlloc<C, MOp, ExtraOut> {
pub fn mop_ty(self) -> MOp {
self.input.data.HdlSome.mop
}
pub fn extra_out_ty(self) -> ExtraOut {
self.output.HdlSome.extra_out_ty()
}
}
#[hdl(cmp_eq)]
pub struct UnitResultCompleted<ExtraOut> {
pub value: PRegValue,
pub extra_out: ExtraOut,
}
#[hdl(cmp_eq, no_static)]
pub struct UnitOutputWrite<C: Type + CpuConfigType> {
pub dest: UnitOutRegNum<C>,
pub value: PRegValue,
}
#[hdl]
pub type UnitOutputWrites<C: Type + CpuConfigType> =
ArrayType<HdlOption<UnitOutputWrite<C>>, UnitCount<C>>;
#[hdl(cmp_eq)]
pub struct TrapData {
// TODO
}
#[hdl]
pub enum UnitResult<ExtraOut> {
Completed(UnitResultCompleted<ExtraOut>),
Trap(TrapData),
}
impl<ExtraOut: Type> UnitResult<ExtraOut> {
pub fn extra_out_ty(self) -> ExtraOut {
self.Completed.extra_out
}
}
#[hdl(no_static)]
pub struct UnitOutput<C: Type + CpuConfigType, ExtraOut> {
pub dest: UnitOutRegNum<C>,
pub retire_queue_index: RetireQueueIndex<C>,
pub result: UnitResult<ExtraOut>,
}
impl<C: Type + CpuConfigType, ExtraOut: Type> UnitOutput<C, ExtraOut> {
pub fn extra_out_ty(self) -> ExtraOut {
self.result.extra_out_ty()
}
}
#[hdl(cmp_eq, no_static)]
pub struct UnitCancelInput<C: Type + CpuConfigType> {
pub target: RetireQueueIndex<C>,
}
impl<C: Type + CpuConfigType> UnitCancelInput<C> {
pub fn is_canceled(
this: impl ToExpr<Type = Self>,
insn_retire_queue_index: impl ToExpr<Type = RetireQueueIndex<C>>,
) -> Expr<Bool> {
RetireQueueIndex::insns_until(insn_retire_queue_index, this.to_expr().target).cmp_ge(0i8)
}
}
@ -343,19 +382,32 @@ pub trait UnitTrait:
'static + Send + Sync + std::fmt::Debug + fayalite::intern::SupportsPtrEqWithTypeId
{
type Type: BundleType;
type ExtraOut: Type;
type MOp: Type;
fn ty(&self) -> Self::Type;
fn extra_out_ty(&self) -> Self::ExtraOut;
fn mop_ty(&self) -> Self::MOp;
fn unit_kind(&self) -> UnitKind;
fn extract_mop(&self, mop: Expr<RenamedMOp<DynSize>>) -> Expr<HdlOption<Self::MOp>>;
fn module(&self) -> Interned<Module<Self::Type>>;
fn cd(&self, this: Expr<Self::Type>) -> Option<Expr<ClockDomain>>;
fn from_execute(
fn unit_to_reg_alloc(
&self,
this: Expr<Self::Type>,
) -> Expr<ExecuteToUnitInterface<PhantomConst<CpuConfig>>>;
) -> Expr<UnitToRegAlloc<PhantomConst<CpuConfig>, Self::MOp, Self::ExtraOut>>;
fn cd(&self, this: Expr<Self::Type>) -> Expr<ClockDomain>;
fn global_state(&self, this: Expr<Self::Type>) -> Expr<GlobalState>;
fn to_dyn(&self) -> DynUnit;
}
type DynUnitTrait = dyn UnitTrait<Type = Bundle>;
type DynUnitTrait = dyn UnitTrait<Type = Bundle, ExtraOut = CanonicalType, MOp = CanonicalType>;
impl fayalite::intern::InternedCompare for DynUnitTrait {
type InternedCompareKey = fayalite::intern::PtrEqWithTypeId;
@ -367,34 +419,54 @@ impl fayalite::intern::InternedCompare for DynUnitTrait {
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct DynUnit {
ty: Bundle,
extra_out_ty: CanonicalType,
mop_ty: CanonicalType,
unit_kind: UnitKind,
unit: Interned<DynUnitTrait>,
}
impl UnitTrait for DynUnit {
type Type = Bundle;
type ExtraOut = CanonicalType;
type MOp = CanonicalType;
fn ty(&self) -> Self::Type {
self.ty
}
fn extra_out_ty(&self) -> Self::ExtraOut {
self.extra_out_ty
}
fn mop_ty(&self) -> Self::MOp {
self.mop_ty
}
fn unit_kind(&self) -> UnitKind {
self.unit_kind
}
fn extract_mop(&self, mop: Expr<RenamedMOp<DynSize>>) -> Expr<HdlOption<Self::MOp>> {
self.unit.extract_mop(mop)
}
fn module(&self) -> Interned<Module<Self::Type>> {
self.unit.module()
}
fn cd(&self, this: Expr<Self::Type>) -> Option<Expr<ClockDomain>> {
fn unit_to_reg_alloc(
&self,
this: Expr<Self::Type>,
) -> Expr<UnitToRegAlloc<PhantomConst<CpuConfig>, Self::MOp, Self::ExtraOut>> {
self.unit.unit_to_reg_alloc(this)
}
fn cd(&self, this: Expr<Self::Type>) -> Expr<ClockDomain> {
self.unit.cd(this)
}
fn from_execute(
&self,
this: Expr<Self::Type>,
) -> Expr<ExecuteToUnitInterface<PhantomConst<CpuConfig>>> {
self.unit.from_execute(this)
fn global_state(&self, this: Expr<Self::Type>) -> Expr<GlobalState> {
self.unit.global_state(this)
}
fn to_dyn(&self) -> DynUnit {
@ -407,34 +479,56 @@ pub struct DynUnitWrapper<T>(pub T);
impl<T: UnitTrait + Clone + std::hash::Hash + Eq> UnitTrait for DynUnitWrapper<T> {
type Type = Bundle;
type ExtraOut = CanonicalType;
type MOp = CanonicalType;
fn ty(&self) -> Self::Type {
Bundle::from_canonical(self.0.ty().canonical())
}
fn extra_out_ty(&self) -> Self::ExtraOut {
self.0.extra_out_ty().canonical()
}
fn mop_ty(&self) -> Self::MOp {
self.0.mop_ty().canonical()
}
fn unit_kind(&self) -> UnitKind {
self.0.unit_kind()
}
fn extract_mop(&self, mop: Expr<RenamedMOp<DynSize>>) -> Expr<HdlOption<Self::MOp>> {
Expr::from_enum(Expr::as_enum(self.0.extract_mop(mop)))
}
fn module(&self) -> Interned<Module<Self::Type>> {
self.0.module().canonical().intern_sized()
}
fn cd(&self, this: Expr<Self::Type>) -> Option<Expr<ClockDomain>> {
fn unit_to_reg_alloc(
&self,
this: Expr<Self::Type>,
) -> Expr<UnitToRegAlloc<PhantomConst<CpuConfig>, Self::MOp, Self::ExtraOut>> {
Expr::from_bundle(Expr::as_bundle(
self.0.unit_to_reg_alloc(Expr::from_bundle(this)),
))
}
fn cd(&self, this: Expr<Self::Type>) -> Expr<ClockDomain> {
self.0.cd(Expr::from_bundle(this))
}
fn from_execute(
&self,
this: Expr<Self::Type>,
) -> Expr<ExecuteToUnitInterface<PhantomConst<CpuConfig>>> {
self.0.from_execute(Expr::from_bundle(this))
fn global_state(&self, this: Expr<Self::Type>) -> Expr<GlobalState> {
self.0.global_state(Expr::from_bundle(this))
}
fn to_dyn(&self) -> DynUnit {
let unit = self.intern();
DynUnit {
ty: unit.ty(),
extra_out_ty: unit.extra_out_ty(),
mop_ty: unit.mop_ty(),
unit_kind: unit.unit_kind(),
unit: Interned::cast_unchecked(unit, |v: &Self| -> &DynUnitTrait { v }),
}

View file

@ -2,35 +2,30 @@
// See Notices.txt for copyright information
use crate::{
config::{CpuConfig, PhantomConstCpuConfig},
config::{CpuConfig, PRegNumWidth},
instruction::{
AddSubMOp, AluBranchMOp, AluCommonMOp, BranchMOp, COMMON_MOP_SRC_LEN, CommonMOp,
CommonMOpDefaultImm, CompareMOp, ConditionMode, LogicalFlagsMOp, LogicalMOp,
OutputIntegerMode, PRegNum, ReadSpecialMOp, ShiftRotateMOp,
AddSubMOp, AluBranchMOp, AluCommonMOp, CommonMOp, LogicalMOp, OutputIntegerMode,
RenamedMOp, COMMON_MOP_SRC_LEN,
},
next_pc::CallStackOp,
register::{
FlagsMode, PRegFlags, PRegFlagsPowerISA, PRegFlagsPowerISAView, PRegFlagsViewTrait,
PRegFlagsX86, PRegFlagsX86View, PRegValue, ViewUnused,
register::{FlagsMode, PRegFlagsPowerISA, PRegFlagsX86, PRegValue},
unit::{
unit_base::{unit_base, ExecuteEnd, ExecuteStart},
DynUnit, DynUnitWrapper, GlobalState, UnitKind, UnitMOp, UnitOutput, UnitResult,
UnitResultCompleted, UnitToRegAlloc, UnitTrait,
},
rename_execute_retire::{
ExecuteToUnitInterface, GlobalState, NextPcPredictorOp, RenamedMOp, UnitCausedCancel,
UnitFinishCauseCancel, UnitInputsReady, UnitOutputReady,
},
unit::{DynUnit, DynUnitWrapper, UnitKind, UnitTrait},
};
use fayalite::{intern::Interned, module::wire_with_loc, prelude::*};
use fayalite::{
intern::Interned, module::wire_with_loc, prelude::*, util::ready_valid::ReadyValid,
};
use std::{collections::HashMap, ops::RangeTo};
#[hdl]
fn add_sub<C: PhantomConstCpuConfig, SrcCount: KnownSize>(
global_state: Expr<GlobalState>,
fn add_sub<SrcCount: KnownSize>(
mop: Expr<AddSubMOp<(), DynSize, SrcCount>>,
pc: Expr<UInt<64>>,
mop: Expr<AddSubMOp<PRegNum<C>, PRegNum<C>, SrcCount>>,
src_values: Expr<Array<TraceAsString<PRegValue>, { COMMON_MOP_SRC_LEN }>>,
) -> Expr<TraceAsString<PRegValue>> {
#[hdl]
let GlobalState { flags_mode } = global_state;
flags_mode: Expr<FlagsMode>,
src_values: Expr<Array<PRegValue, { COMMON_MOP_SRC_LEN }>>,
) -> Expr<UnitResultCompleted<()>> {
#[hdl]
let AddSubMOp::<_, _, _> {
alu_common,
@ -40,11 +35,11 @@ fn add_sub<C: PhantomConstCpuConfig, SrcCount: KnownSize>(
add_pc,
} = mop;
#[hdl]
let AluCommonMOp::<_, _, _, _> {
let AluCommonMOp::<_, _, _> {
common,
output_integer_mode,
} = alu_common;
let imm = CommonMOpDefaultImm::as_sint_dyn(common.imm).cast_to_static::<UInt<64>>();
let imm: Expr<UInt<64>> = CommonMOp::imm(common).cast_to_static();
#[hdl]
let carry_in_before_inversion = wire();
connect(carry_in_before_inversion, false);
@ -58,13 +53,13 @@ fn add_sub<C: PhantomConstCpuConfig, SrcCount: KnownSize>(
FlagsMode::PowerISA(_) => {
connect(
carry_in_before_inversion,
PRegFlagsPowerISA::view(src_values[1].flags).xer_ca,
PRegFlagsPowerISA::xer_ca(src_values[1].flags),
);
}
FlagsMode::X86(_) => {
connect(
carry_in_before_inversion,
PRegFlagsX86::view(src_values[1].flags).cf,
PRegFlagsX86::cf(src_values[1].flags),
);
}
}
@ -201,482 +196,142 @@ fn add_sub<C: PhantomConstCpuConfig, SrcCount: KnownSize>(
#[hdl]
match flags_mode {
FlagsMode::PowerISA(_) => {
connect(
flags,
PRegFlagsPowerISA::from_view(PRegFlagsPowerISAView {
unused: ViewUnused::splat(false.to_expr()),
xer_ca: pwr_ca,
xer_ca32: pwr_ca32,
xer_ov: pwr_ov,
xer_ov32: pwr_ov32,
so: pwr_so,
cr_lt: pwr_cr_lt,
cr_gt: pwr_cr_gt,
cr_eq: pwr_cr_eq,
}),
);
PRegFlagsPowerISA::clear_unused(flags);
connect(PRegFlagsPowerISA::xer_ca(flags), pwr_ca);
connect(PRegFlagsPowerISA::xer_ca32(flags), pwr_ca32);
connect(PRegFlagsPowerISA::xer_ov(flags), pwr_ov);
connect(PRegFlagsPowerISA::xer_ov32(flags), pwr_ov32);
connect(PRegFlagsPowerISA::cr_lt(flags), pwr_cr_lt);
connect(PRegFlagsPowerISA::cr_gt(flags), pwr_cr_gt);
connect(PRegFlagsPowerISA::cr_eq(flags), pwr_cr_eq);
connect(PRegFlagsPowerISA::so(flags), pwr_so);
}
FlagsMode::X86(_) => {
connect(
flags,
PRegFlagsX86::from_view(PRegFlagsX86View {
unused: ViewUnused::splat(false.to_expr()),
cf: x86_cf,
zf: x86_zf,
sf: x86_sf,
of: x86_of,
af: x86_af,
pf: x86_pf,
// this insn doesn't write DF, so it's output isn't used for reading DF
df: false.to_expr(),
}),
);
PRegFlagsX86::clear_unused(flags);
connect(PRegFlagsX86::cf(flags), x86_cf);
connect(PRegFlagsX86::af(flags), x86_af);
connect(PRegFlagsX86::of(flags), x86_of);
connect(PRegFlagsX86::sf(flags), x86_sf);
connect(PRegFlagsX86::pf(flags), x86_pf);
connect(PRegFlagsX86::zf(flags), x86_zf);
// this insn doesn't write DF, so it's output isn't used for reading DF
connect(PRegFlagsX86::df(flags), false);
}
}
let retval = #[hdl]
PRegValue { int_fp, flags };
retval.to_trace_as_string()
#[hdl]
UnitResultCompleted::<_> {
value: #[hdl]
PRegValue { int_fp, flags },
extra_out: (),
}
}
#[hdl]
fn logical_flags<C: PhantomConstCpuConfig>(
global_state: Expr<GlobalState>,
pc: Expr<UInt<64>>,
mop: Expr<LogicalFlagsMOp<PRegNum<C>, PRegNum<C>>>,
src_values: Expr<Array<TraceAsString<PRegValue>, { COMMON_MOP_SRC_LEN }>>,
) -> Expr<TraceAsString<PRegValue>> {
#[hdl]
let GlobalState { flags_mode } = global_state;
fn logical(
mop: Expr<LogicalMOp<(), DynSize>>,
flags_mode: Expr<FlagsMode>,
src_values: Expr<Array<PRegValue, { COMMON_MOP_SRC_LEN }>>,
) -> Expr<UnitResultCompleted<()>> {
// TODO: finish
PRegValue::zeroed().to_trace_as_string()
}
#[hdl]
fn logical<C: PhantomConstCpuConfig>(
global_state: Expr<GlobalState>,
pc: Expr<UInt<64>>,
mop: Expr<LogicalMOp<PRegNum<C>, PRegNum<C>, ConstUsize<2>>>,
src_values: Expr<Array<TraceAsString<PRegValue>, { COMMON_MOP_SRC_LEN }>>,
) -> Expr<TraceAsString<PRegValue>> {
#[hdl]
let GlobalState { flags_mode } = global_state;
// TODO: finish
PRegValue::zeroed().to_trace_as_string()
}
#[hdl]
fn logical_i<C: PhantomConstCpuConfig>(
global_state: Expr<GlobalState>,
pc: Expr<UInt<64>>,
mop: Expr<LogicalMOp<PRegNum<C>, PRegNum<C>, ConstUsize<1>>>,
src_values: Expr<Array<TraceAsString<PRegValue>, { COMMON_MOP_SRC_LEN }>>,
) -> Expr<TraceAsString<PRegValue>> {
#[hdl]
let GlobalState { flags_mode } = global_state;
// TODO: finish
PRegValue::zeroed().to_trace_as_string()
}
#[hdl]
fn shift_rotate<C: PhantomConstCpuConfig>(
global_state: Expr<GlobalState>,
pc: Expr<UInt<64>>,
mop: Expr<ShiftRotateMOp<PRegNum<C>, PRegNum<C>>>,
src_values: Expr<Array<TraceAsString<PRegValue>, { COMMON_MOP_SRC_LEN }>>,
) -> Expr<TraceAsString<PRegValue>> {
#[hdl]
let GlobalState { flags_mode } = global_state;
// TODO: finish
PRegValue::zeroed().to_trace_as_string()
}
#[hdl]
fn compare<C: PhantomConstCpuConfig, SrcCount: KnownSize>(
global_state: Expr<GlobalState>,
pc: Expr<UInt<64>>,
mop: Expr<CompareMOp<PRegNum<C>, PRegNum<C>, SrcCount>>,
src_values: Expr<Array<TraceAsString<PRegValue>, { COMMON_MOP_SRC_LEN }>>,
) -> Expr<TraceAsString<PRegValue>> {
#[hdl]
let GlobalState { flags_mode } = global_state;
// TODO: finish
PRegValue::zeroed().to_trace_as_string()
}
#[hdl]
fn branch<C: PhantomConstCpuConfig, SrcCount: KnownSize>(
global_state: Expr<GlobalState>,
pc: Expr<UInt<64>>,
fallthrough_pc: Expr<UInt<64>>,
predicted_next_pc: Expr<UInt<64>>,
mop: Expr<BranchMOp<PRegNum<C>, PRegNum<C>, SrcCount>>,
src_values: Expr<Array<TraceAsString<PRegValue>, { COMMON_MOP_SRC_LEN }>>,
config: C,
) -> (
Expr<TraceAsString<PRegValue>>,
Expr<NextPcPredictorOp<C>>,
Expr<HdlOption<UnitCausedCancel<C>>>,
) {
#[hdl]
let GlobalState { flags_mode } = global_state;
#[hdl]
let BranchMOp::<_, _, _> {
common,
invert_src0_cond,
src0_cond_mode,
invert_src2_eq_zero,
pc_relative,
is_call,
is_ret,
} = mop;
#[hdl]
let CommonMOp::<_, _, _, _, _> {
prefix_pad: _,
dest: _,
src,
imm,
} = common;
let [src0, src1, src2] = *src_values;
#[hdl]
let has_src0 = wire();
if let Some(src0) = src.as_ref().get(0) {
connect(has_src0, src0.cmp_ne(src0.ty().const_zero()));
} else {
connect(has_src0, false);
UnitResultCompleted::<_> {
value: PRegValue::zeroed(),
extra_out: (),
}
#[hdl]
let has_src2 = wire();
if let Some(src2) = src.as_ref().get(2) {
connect(has_src2, src2.cmp_ne(src2.ty().const_zero()));
} else {
connect(has_src2, false);
}
#[hdl]
let src2_cond = wire();
#[hdl]
if has_src2 {
#[hdl]
if invert_src2_eq_zero {
connect(src2_cond, src2.int_fp.cmp_ne(0u64));
} else {
connect(src2_cond, src2.int_fp.cmp_eq(0u64));
}
} else {
connect(src2_cond, true);
}
#[hdl]
let src0_cond = wire();
#[hdl]
match flags_mode {
FlagsMode::PowerISA(_) => {
let src0_flags = PRegFlags::view::<PRegFlagsPowerISA>(src0.flags);
#[hdl]
match src0_cond_mode {
ConditionMode::Eq => connect(src0_cond, src0_flags.cr_eq),
ConditionMode::ULt => connect(src0_cond, src0_flags.cr_lt),
ConditionMode::UGt => connect(src0_cond, src0_flags.cr_gt),
ConditionMode::SLt => connect(src0_cond, src0_flags.cr_lt),
ConditionMode::SGt => connect(src0_cond, src0_flags.cr_gt),
ConditionMode::Sign => connect(src0_cond, false),
ConditionMode::Overflow => connect(src0_cond, src0_flags.so),
ConditionMode::Parity => connect(src0_cond, false),
};
}
FlagsMode::X86(_) => {
let src0_flags = PRegFlags::view::<PRegFlagsX86>(src0.flags);
#[hdl]
match src0_cond_mode {
ConditionMode::Eq => connect(src0_cond, src0_flags.zf),
ConditionMode::ULt => connect(src0_cond, src0_flags.cf),
ConditionMode::UGt => connect(src0_cond, !src0_flags.zf & !src0_flags.cf),
ConditionMode::SLt => connect(src0_cond, src0_flags.sf.cmp_ne(src0_flags.of)),
ConditionMode::SGt => connect(
src0_cond,
!src0_flags.zf & src0_flags.sf.cmp_eq(src0_flags.of),
),
ConditionMode::Sign => connect(src0_cond, src0_flags.sf),
ConditionMode::Overflow => connect(src0_cond, src0_flags.of),
ConditionMode::Parity => connect(src0_cond, src0_flags.pf),
};
}
}
let src0_cond = src0_cond ^ invert_src0_cond;
#[hdl]
let pc_or_zero = wire();
#[hdl]
if pc_relative {
connect(pc_or_zero, pc);
} else {
connect(pc_or_zero, 0u64);
}
#[hdl]
let target_pc: UInt<64> = wire();
connect_any(
target_pc,
src1.int_fp
+ CommonMOpDefaultImm::as_sint_dyn(imm).cast_to_static::<UInt<64>>()
+ pc_or_zero,
);
#[hdl]
let cond_br_taken = wire();
connect(cond_br_taken, src0_cond & src2_cond);
#[hdl]
let is_cond = wire();
connect(is_cond, !cond_br_taken | has_src0 | has_src2);
#[hdl]
let next_pc = wire();
#[hdl]
if cond_br_taken {
connect(next_pc, target_pc);
} else {
connect(next_pc, fallthrough_pc);
};
#[hdl]
let cancel = wire(HdlOption[UnitCausedCancel[config]]);
#[hdl]
if next_pc.cmp_ne(predicted_next_pc) {
connect(
cancel,
HdlSome(
#[hdl]
UnitCausedCancel::<C> {
start_at_pc: next_pc,
cancel_after_retire: true,
config,
},
),
);
} else {
connect(cancel, cancel.ty().HdlNone());
};
let fallthrough_pc_value = #[hdl]
PRegValue {
int_fp: fallthrough_pc,
flags: PRegFlags::zeroed_sim(),
};
#[hdl]
let call_stack_op = wire();
#[hdl]
if is_ret {
connect(call_stack_op, CallStackOp.Pop());
} else if is_call {
connect(call_stack_op, CallStackOp.Push(fallthrough_pc));
} else {
connect(call_stack_op, CallStackOp.None());
}
#[hdl]
let cond_br_taken_opt = wire();
#[hdl]
if is_cond {
connect(cond_br_taken_opt, HdlSome(cond_br_taken));
} else {
connect(cond_br_taken_opt, HdlNone());
}
(
fallthrough_pc_value.into_trace_as_string(),
#[hdl]
NextPcPredictorOp::<_> {
call_stack_op,
cond_br_taken: cond_br_taken_opt,
config,
},
cancel,
)
}
#[hdl]
fn read_special<C: PhantomConstCpuConfig>(
global_state: Expr<GlobalState>,
pc: Expr<UInt<64>>,
mop: Expr<ReadSpecialMOp<PRegNum<C>, PRegNum<C>>>,
src_values: Expr<Array<TraceAsString<PRegValue>, { COMMON_MOP_SRC_LEN }>>,
) -> Expr<TraceAsString<PRegValue>> {
#[hdl]
let GlobalState { flags_mode } = global_state;
// TODO: finish
PRegValue::zeroed().to_trace_as_string()
}
#[hdl_module]
pub fn alu_branch(config: PhantomConst<CpuConfig>, unit_index: usize) {
#[hdl]
let from_execute: ExecuteToUnitInterface<PhantomConst<CpuConfig>> =
m.input(ExecuteToUnitInterface[config]);
assert_eq!(config.get().units[unit_index].kind, UnitKind::AluBranch);
let cd: ClockDomain = m.input();
#[hdl]
let unit_to_reg_alloc: UnitToRegAlloc<
PhantomConst<CpuConfig>,
AluBranchMOp<(), DynSize>,
(),
> = m.output(UnitToRegAlloc[config][AluBranchMOp[()][PRegNumWidth[config]]][()]);
#[hdl]
let global_state: GlobalState = m.input();
#[hdl]
let ExecuteToUnitInterface::<_> {
global_state,
enqueue,
inputs_ready,
is_no_longer_speculative: _, // we don't care about being speculative for these instructions
cant_cause_cancel,
output_ready,
finish_cause_cancel,
unit_outputs_ready: _,
cancel_all,
config: _,
} = from_execute;
// we ignore enqueues since we don't need to track order for these instructions
connect(enqueue.ready, true);
let unit_base = instance(unit_base(
config,
unit_index,
Expr::ty(unit_to_reg_alloc).input.data.HdlSome.mop,
(),
));
connect(unit_to_reg_alloc, unit_base.unit_to_reg_alloc);
connect(unit_base.cd, cd);
connect(unit_base.execute_start.ready, true);
connect(
unit_base.execute_end,
Expr::ty(unit_base.execute_end).HdlNone(),
);
#[hdl]
if let HdlSome(inputs_ready) = inputs_ready {
if let HdlSome(execute_start) = ReadyValid::firing_data(unit_base.execute_start) {
#[hdl]
let UnitInputsReady::<_> {
mop: mop_instance,
src_values,
config: _,
} = inputs_ready;
let ExecuteStart::<_, _> { insn, src_values } = execute_start;
#[hdl]
let fallthrough_pc: UInt<64> = wire();
connect_any(fallthrough_pc, mop_instance.pc + mop_instance.size_in_bytes);
#[hdl]
match *mop_instance.mop {
RenamedMOp::<_>::AluBranch(mop) => {
#[hdl]
let dest_value = wire();
#[hdl]
let predictor_op = wire(NextPcPredictorOp[config]);
connect(
predictor_op,
match insn.mop {
AluBranchMOp::<_, _>::AddSub(mop) => connect(
unit_base.execute_end,
HdlSome(
#[hdl]
NextPcPredictorOp::<_> {
call_stack_op: CallStackOp.None(),
cond_br_taken: HdlNone(),
config,
ExecuteEnd::<_, _> {
unit_output: #[hdl]
UnitOutput::<_, _> {
dest: insn.dest,
retire_queue_index: insn.retire_queue_index,
result: UnitResult[()].Completed(add_sub(
mop,
insn.pc,
global_state.flags_mode,
src_values,
)),
},
},
);
#[hdl]
let caused_cancel = wire(HdlOption[UnitCausedCancel[config]]);
connect(caused_cancel, caused_cancel.ty().HdlNone());
#[hdl]
match mop {
AluBranchMOp::<_, _>::AddSub(mop) => {
connect(
dest_value,
add_sub(global_state, mop_instance.pc, mop, src_values),
);
}
AluBranchMOp::<_, _>::AddSubI(mop) => {
connect(
dest_value,
add_sub(global_state, mop_instance.pc, mop, src_values),
);
}
AluBranchMOp::<_, _>::LogicalFlags(mop) => {
connect(
dest_value,
logical_flags(global_state, mop_instance.pc, mop, src_values),
);
}
AluBranchMOp::<_, _>::Logical(mop) => {
connect(
dest_value,
logical(global_state, mop_instance.pc, mop, src_values),
);
}
AluBranchMOp::<_, _>::LogicalI(mop) => {
connect(
dest_value,
logical_i(global_state, mop_instance.pc, mop, src_values),
);
}
AluBranchMOp::<_, _>::ShiftRotate(mop) => {
connect(
dest_value,
shift_rotate(global_state, mop_instance.pc, mop, src_values),
);
}
AluBranchMOp::<_, _>::Compare(mop) => {
connect(
dest_value,
compare(global_state, mop_instance.pc, mop, src_values),
);
}
AluBranchMOp::<_, _>::CompareI(mop) => {
connect(
dest_value,
compare(global_state, mop_instance.pc, mop, src_values),
);
}
AluBranchMOp::<_, _>::Branch(mop) => {
let (dest_value_, predictor_op_, caused_cancel_) = branch(
global_state,
mop_instance.pc,
fallthrough_pc,
mop_instance.predicted_next_pc,
mop,
src_values,
config,
);
connect(dest_value, dest_value_);
connect(predictor_op, predictor_op_);
connect(caused_cancel, caused_cancel_);
}
AluBranchMOp::<_, _>::BranchI(mop) => {
let (dest_value_, predictor_op_, caused_cancel_) = branch(
global_state,
mop_instance.pc,
fallthrough_pc,
mop_instance.predicted_next_pc,
mop,
src_values,
config,
);
connect(dest_value, dest_value_);
connect(predictor_op, predictor_op_);
connect(caused_cancel, caused_cancel_);
}
AluBranchMOp::<_, _>::ReadSpecial(mop) => {
connect(
dest_value,
read_special(global_state, mop_instance.pc, mop, src_values),
);
}
}
connect(
output_ready,
HdlSome(
#[hdl]
UnitOutputReady::<_> {
id: mop_instance.id,
dest_value,
predictor_op,
),
),
AluBranchMOp::<_, _>::AddSubI(mop) => connect(
unit_base.execute_end,
HdlSome(
#[hdl]
ExecuteEnd::<_, _> {
unit_output: #[hdl]
UnitOutput::<_, _> {
dest: insn.dest,
retire_queue_index: insn.retire_queue_index,
result: UnitResult[()].Completed(add_sub(
mop,
insn.pc,
global_state.flags_mode,
src_values,
)),
},
),
);
connect(
finish_cause_cancel,
HdlSome(
#[hdl]
UnitFinishCauseCancel::<_> {
id: mop_instance.id,
caused_cancel,
config,
},
),
),
AluBranchMOp::<_, _>::Logical(mop) => connect(
unit_base.execute_end,
HdlSome(
#[hdl]
ExecuteEnd::<_, _> {
unit_output: #[hdl]
UnitOutput::<_, _> {
dest: insn.dest,
retire_queue_index: insn.retire_queue_index,
result: UnitResult[()].Completed(logical(
mop,
global_state.flags_mode,
src_values,
)),
},
),
);
}
_ => {
// error
connect(output_ready, output_ready.ty().HdlNone());
connect(finish_cause_cancel, finish_cause_cancel.ty().HdlNone());
}
},
),
),
}
} else {
connect(output_ready, output_ready.ty().HdlNone());
connect(finish_cause_cancel, finish_cause_cancel.ty().HdlNone());
}
// all outputs are immediately ready, so reporting that instructions can't cause cancels is superfluous
connect(cant_cause_cancel, cant_cause_cancel.ty().HdlNone());
// this unit is purely combinational so canceling does nothing
connect(cancel_all.ready, true);
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
@ -696,28 +351,46 @@ impl AluBranch {
impl UnitTrait for AluBranch {
type Type = alu_branch;
type ExtraOut = ();
type MOp = AluBranchMOp<(), DynSize>;
fn ty(&self) -> Self::Type {
self.module.io_ty()
}
fn extra_out_ty(&self) -> Self::ExtraOut {
()
}
fn mop_ty(&self) -> Self::MOp {
self.module.io_ty().unit_to_reg_alloc.mop_ty()
}
fn unit_kind(&self) -> UnitKind {
UnitKind::AluBranch
}
fn extract_mop(&self, mop: Expr<RenamedMOp<DynSize>>) -> Expr<HdlOption<Self::MOp>> {
UnitMOp::alu_branch_mop(mop)
}
fn module(&self) -> Interned<Module<Self::Type>> {
self.module
}
fn cd(&self, this: Expr<Self::Type>) -> Option<Expr<ClockDomain>> {
None
}
fn from_execute(
fn unit_to_reg_alloc(
&self,
this: Expr<Self::Type>,
) -> Expr<ExecuteToUnitInterface<PhantomConst<CpuConfig>>> {
this.from_execute
) -> Expr<UnitToRegAlloc<PhantomConst<CpuConfig>, Self::MOp, Self::ExtraOut>> {
this.unit_to_reg_alloc
}
fn cd(&self, this: Expr<Self::Type>) -> Expr<ClockDomain> {
this.cd
}
fn global_state(&self, this: Expr<Self::Type>) -> Expr<GlobalState> {
this.global_state
}
fn to_dyn(&self) -> DynUnit {

View file

@ -0,0 +1,666 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
config::{CpuConfig, CpuConfigType, UnitOutRegNumWidth},
instruction::{MOpTrait, PRegNum, UnitNum, UnitOutRegNum, COMMON_MOP_SRC_LEN},
register::PRegValue,
unit::{
RenamedInsnData, RetireQueueIndex, UnitCancelInput, UnitForwardingInfo, UnitOutput,
UnitOutputWrite, UnitToRegAlloc,
},
};
use fayalite::{
memory::splat_mask,
module::{memory_with_loc, wire_with_loc},
prelude::*,
ty::StaticType,
util::{prefix_sum::reduce, ready_valid::ReadyValid},
};
#[hdl(no_static)]
pub struct ExecuteStart<C: Type + CpuConfigType, MOp: Type> {
pub insn: RenamedInsnData<C, MOp, UnitOutRegNum<C>>,
pub src_values: Array<PRegValue, { COMMON_MOP_SRC_LEN }>,
}
#[hdl(no_static)]
pub struct ExecuteEnd<C: Type + CpuConfigType, ExtraOut> {
pub unit_output: UnitOutput<C, ExtraOut>,
}
#[hdl]
enum InFlightOpState {
Ready,
Running,
CanceledAndRunning,
}
impl InFlightOpState {
fn ready_next_state(canceling: bool, starting: bool, ending: bool) -> Expr<HdlOption<Self>> {
match (canceling, starting, ending) {
(false, false, _) => HdlSome(InFlightOpState.Ready()),
(false, true, false) => HdlSome(InFlightOpState.Running()),
(false, true, true) => HdlNone(),
(true, false, _) => HdlNone(),
(true, true, false) => HdlSome(InFlightOpState.CanceledAndRunning()),
(true, true, true) => HdlNone(),
}
}
fn running_next_state(canceling: bool, _starting: bool, ending: bool) -> Expr<HdlOption<Self>> {
match (canceling, ending) {
(false, false) => HdlSome(InFlightOpState.Running()),
(false, true) => HdlNone(),
(true, false) => HdlSome(InFlightOpState.CanceledAndRunning()),
(true, true) => HdlNone(),
}
}
fn canceled_and_running_next_state(
_canceling: bool,
_starting: bool,
ending: bool,
) -> Expr<HdlOption<Self>> {
if ending {
HdlNone()
} else {
HdlSome(InFlightOpState.CanceledAndRunning())
}
}
/// FIXME: this is working around #[hdl] match not supporting matching values inside structs yet
#[hdl]
fn connect_next_state(
canceling: Expr<Bool>,
starting: Expr<Bool>,
ending: Expr<Bool>,
next_state_fn: fn(canceling: bool, starting: bool, ending: bool) -> Expr<HdlOption<Self>>,
next_state: Expr<HdlOption<Self>>,
) {
#[hdl]
fn recurse<const N: usize>(
exprs: &[Expr<Bool>; N],
bools: &mut [bool; N],
f: &mut impl FnMut(&[bool; N]),
arg_index: usize,
) {
if arg_index < N {
#[hdl]
if exprs[arg_index] {
bools[arg_index] = true;
recurse(exprs, bools, f, arg_index + 1);
} else {
bools[arg_index] = false;
recurse(exprs, bools, f, arg_index + 1);
}
} else {
f(bools);
}
}
recurse(
&[canceling, starting, ending],
&mut [false; 3],
&mut |&[canceling, starting, ending]| {
connect(next_state, next_state_fn(canceling, starting, ending))
},
0,
);
}
}
#[hdl(no_static)]
struct InFlightOp<C: Type + CpuConfigType, MOp: Type> {
state: InFlightOpState,
insn: RenamedInsnData<C, MOp, UnitOutRegNum<C>>,
src_ready_flags: Array<Bool, { COMMON_MOP_SRC_LEN }>,
}
impl<C: Type + CpuConfigType, MOp: Type> InFlightOp<C, MOp> {
fn config(self) -> C {
self.insn.retire_queue_index.config
}
}
#[hdl(no_static)]
struct InFlightOpsSummary<C: Type + CpuConfigType, OpIndexWidth: Size> {
empty_op_index: HdlOption<UIntType<OpIndexWidth>>,
ready_op_index: HdlOption<UIntType<OpIndexWidth>>,
ready_for_retire_queue_index: HdlOption<RetireQueueIndex<C>>,
}
impl<C: Type + CpuConfigType, OpIndexWidth: Size> InFlightOpsSummary<C, OpIndexWidth> {
#[hdl]
fn new<MOp: Type>(
op_index: usize,
op_index_ty: UIntType<OpIndexWidth>,
in_flight_op: impl ToExpr<Type = HdlOption<InFlightOp<C, MOp>>>,
) -> Expr<Self> {
let in_flight_op = in_flight_op.to_expr();
let empty_op_index = wire_with_loc(
&format!("empty_op_index_{op_index}"),
SourceLocation::caller(),
HdlOption[op_index_ty],
);
connect(empty_op_index, HdlOption[op_index_ty].HdlNone());
let ready_op_index = wire_with_loc(
&format!("ready_op_index_{op_index}"),
SourceLocation::caller(),
HdlOption[op_index_ty],
);
connect(ready_op_index, HdlOption[op_index_ty].HdlNone());
let ready_for_retire_queue_index = wire_with_loc(
&format!("ready_for_retire_queue_index_{op_index}"),
SourceLocation::caller(),
HdlOption[RetireQueueIndex[Expr::ty(in_flight_op).HdlSome.config()]],
);
connect(
ready_for_retire_queue_index,
Expr::ty(ready_for_retire_queue_index).HdlNone(),
);
#[hdl]
if let HdlSome(in_flight_op) = in_flight_op {
#[hdl]
let InFlightOp::<_, _> {
state,
insn,
src_ready_flags,
} = in_flight_op;
connect(ready_op_index, HdlOption[op_index_ty].HdlNone());
let ready_for_retire_queue_index_value = wire_with_loc(
&format!("ready_for_retire_queue_index_value_{op_index}"),
SourceLocation::caller(),
RetireQueueIndex[Expr::ty(in_flight_op).config()],
);
connect(ready_for_retire_queue_index_value, insn.retire_queue_index);
// TODO: don't subtract one from `.index` when instruction is ready to
// do something at retire time (e.g. non-speculative stores)
// -- that will need a new InFlightOpState variant.
connect_any(
ready_for_retire_queue_index_value.index,
// subtract one -- this instruction isn't ready to retire,
// but the previous one could be
insn.retire_queue_index.index - 1u8,
);
#[hdl]
match state {
InFlightOpState::Ready => {
#[hdl]
if src_ready_flags.cmp_eq([true; COMMON_MOP_SRC_LEN]) {
connect(ready_op_index, HdlSome(op_index.cast_to(op_index_ty)));
}
connect(
ready_for_retire_queue_index,
HdlSome(ready_for_retire_queue_index_value),
);
}
InFlightOpState::CanceledAndRunning => {
// the instruction has been canceled, but is still
// executing so we treat it as if it doesn't exist
// other than making sure the in_flight_op slot
// isn't reused until execution is done.
}
InFlightOpState::Running => {
connect(
ready_for_retire_queue_index,
HdlSome(ready_for_retire_queue_index_value),
);
}
}
} else {
connect(empty_op_index, HdlSome(op_index.cast_to(op_index_ty)));
}
#[hdl]
InFlightOpsSummary::<_, _> {
empty_op_index,
ready_op_index,
ready_for_retire_queue_index,
}
}
#[hdl]
fn combine(l: impl ToExpr<Type = Self>, r: impl ToExpr<Type = Self>) -> Expr<Self> {
let l = l.to_expr();
let r = r.to_expr();
#[hdl]
let combine_ready_for_retire_queue_index = wire(Expr::ty(l).ready_for_retire_queue_index);
#[hdl]
if let HdlSome(l_ready_for_retire_queue_index) = l.ready_for_retire_queue_index {
#[hdl]
if let HdlSome(r_ready_for_retire_queue_index) = r.ready_for_retire_queue_index {
#[hdl]
if RetireQueueIndex::insns_until(
l_ready_for_retire_queue_index,
r_ready_for_retire_queue_index,
)
.cmp_lt(0i8)
{
connect(
combine_ready_for_retire_queue_index,
l.ready_for_retire_queue_index,
);
} else {
connect(
combine_ready_for_retire_queue_index,
r.ready_for_retire_queue_index,
);
}
} else {
connect(
combine_ready_for_retire_queue_index,
l.ready_for_retire_queue_index,
);
}
} else {
connect(
combine_ready_for_retire_queue_index,
r.ready_for_retire_queue_index,
);
}
#[hdl]
InFlightOpsSummary::<_, _> {
empty_op_index: HdlOption::or(l.empty_op_index, r.empty_op_index),
ready_op_index: HdlOption::or(l.ready_op_index, r.ready_op_index),
ready_for_retire_queue_index: combine_ready_for_retire_queue_index,
}
}
}
impl InFlightOpsSummary<PhantomConst<CpuConfig>, DynSize> {
fn summarize<MOp: Type, MaxInFlight: Size>(
in_flight_ops: impl ToExpr<
Type = ArrayType<HdlOption<InFlightOp<PhantomConst<CpuConfig>, MOp>>, MaxInFlight>,
>,
) -> Expr<Self> {
let in_flight_ops = in_flight_ops.to_expr();
let max_in_flight = Expr::ty(in_flight_ops).len();
let index_range = 0..max_in_flight;
let index_ty = UInt::range(index_range.clone());
reduce(
index_range.map(|i| Self::new(i, index_ty, in_flight_ops[i])),
Self::combine,
)
.expect("in_flight_ops is known to have len > 0")
}
}
#[hdl_module]
pub fn unit_base<MOp: Type + MOpTrait<DestReg = (), SrcRegWidth = DynSize>, ExtraOut: Type>(
config: PhantomConst<CpuConfig>,
unit_index: usize,
mop_ty: MOp,
extra_out_ty: ExtraOut,
) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let unit_to_reg_alloc: UnitToRegAlloc<PhantomConst<CpuConfig>, MOp, ExtraOut> =
m.output(UnitToRegAlloc[config][mop_ty][extra_out_ty]);
#[hdl]
let execute_start: ReadyValid<ExecuteStart<PhantomConst<CpuConfig>, MOp>> =
m.output(ReadyValid[ExecuteStart[config][mop_ty]]);
#[hdl]
let execute_end: HdlOption<ExecuteEnd<PhantomConst<CpuConfig>, ExtraOut>> =
m.input(HdlOption[ExecuteEnd[config][extra_out_ty]]);
connect(execute_start.data, Expr::ty(execute_start).data.HdlNone());
let max_in_flight = config.get().unit_max_in_flight(unit_index).get();
let in_flight_op_ty = InFlightOp[config][mop_ty];
#[hdl]
let in_flight_ops = reg_builder()
.clock_domain(cd)
.reset(repeat(HdlOption[in_flight_op_ty].HdlNone(), max_in_flight));
let in_flight_ops_summary_value = InFlightOpsSummary::summarize(in_flight_ops);
#[hdl]
let in_flight_ops_summary = wire(Expr::ty(in_flight_ops_summary_value));
connect(in_flight_ops_summary, in_flight_ops_summary_value);
connect(
unit_to_reg_alloc.input.ready,
HdlOption::is_some(in_flight_ops_summary.empty_op_index),
);
connect(
unit_to_reg_alloc.ready_for_retire_queue_index,
in_flight_ops_summary.ready_for_retire_queue_index,
);
#[hdl]
let UnitForwardingInfo::<_> {
unit_output_writes,
unit_reg_frees,
} = unit_to_reg_alloc.unit_forwarding_info;
#[hdl]
let read_src_regs = wire(mop_ty.src_regs_ty());
connect(
read_src_regs,
repeat(PRegNum[config].const_zero().cast_to_bits(), ConstUsize),
);
#[hdl]
let read_src_values = wire();
connect(read_src_values, [PRegValue::zeroed(); COMMON_MOP_SRC_LEN]);
#[hdl]
let input_src_regs = wire(mop_ty.src_regs_ty());
connect(
input_src_regs,
repeat(PRegNum[config].const_zero().cast_to_bits(), ConstUsize),
);
#[hdl]
let input_src_regs_valid = wire();
connect(input_src_regs_valid, [true; COMMON_MOP_SRC_LEN]);
let mut unit_output_regs_valid: Vec<MemBuilder<Bool>> = (0..Expr::ty(unit_output_writes).len())
.map(|unit_index| {
let mut mem = memory_with_loc(
&format!("unit_{unit_index}_output_regs_valid"),
Bool,
SourceLocation::caller(),
);
mem.depth(1 << UnitOutRegNumWidth[config]);
mem
})
.collect();
for unit_index in 0..Expr::ty(unit_output_writes).len() {
let mut unit_output_regs = memory_with_loc(
&format!("unit_{unit_index}_output_regs"),
PRegValue,
SourceLocation::caller(),
);
unit_output_regs.depth(1 << UnitOutRegNumWidth[config]);
for src_index in 0..COMMON_MOP_SRC_LEN {
let read_port = unit_output_regs.new_read_port();
let p_reg_num = read_src_regs[src_index].cast_bits_to(PRegNum[config]);
connect_any(read_port.addr, p_reg_num.unit_out_reg.value);
connect(read_port.en, false);
connect(read_port.clk, cd.clk);
#[hdl]
if UnitNum::is_index(p_reg_num.unit_num, unit_index) {
connect(read_port.en, true);
connect(read_src_values[src_index], read_port.data);
}
}
for src_index in 0..COMMON_MOP_SRC_LEN {
let read_port = unit_output_regs_valid[unit_index].new_read_port();
let p_reg_num = input_src_regs[src_index].cast_bits_to(PRegNum[config]);
connect_any(read_port.addr, p_reg_num.unit_out_reg.value);
connect(read_port.en, false);
connect(read_port.clk, cd.clk);
#[hdl]
if UnitNum::is_index(p_reg_num.unit_num, unit_index) {
connect(read_port.en, true);
connect(input_src_regs_valid[src_index], read_port.data);
}
}
let write_port = unit_output_regs.new_write_port();
connect_any(write_port.addr, 0u8);
connect(write_port.en, false);
connect(write_port.clk, cd.clk);
connect(write_port.data, PRegValue::zeroed());
connect(write_port.mask, splat_mask(PRegValue, true.to_expr()));
let ready_write_port = unit_output_regs_valid[unit_index].new_write_port();
connect_any(ready_write_port.addr, 0u8);
connect(ready_write_port.en, false);
connect(ready_write_port.clk, cd.clk);
connect(ready_write_port.data, true);
connect(ready_write_port.mask, true);
#[hdl]
if let HdlSome(unit_output_write) = unit_output_writes[unit_index] {
connect_any(write_port.addr, unit_output_write.dest.value);
connect(write_port.data, unit_output_write.value);
connect(write_port.en, true);
connect_any(ready_write_port.addr, unit_output_write.dest.value);
connect(ready_write_port.en, true);
let p_reg_num = #[hdl]
PRegNum::<_> {
unit_num: UnitNum[config].from_index(unit_index),
unit_out_reg: unit_output_write.dest,
};
for src_index in 0..COMMON_MOP_SRC_LEN {
#[hdl]
if input_src_regs[src_index].cmp_eq(p_reg_num.cast_to_bits()) {
connect(input_src_regs_valid[src_index], true);
}
}
}
let free_write_port = unit_output_regs_valid[unit_index].new_write_port();
connect_any(free_write_port.addr, 0u8);
connect(free_write_port.en, false);
connect(free_write_port.clk, cd.clk);
connect(free_write_port.data, false);
connect(free_write_port.mask, true);
#[hdl]
if let HdlSome(unit_reg_free) = unit_reg_frees[unit_index] {
connect_any(free_write_port.addr, unit_reg_free.value);
connect(free_write_port.en, true);
}
}
#[hdl]
if let HdlSome(ready_op_index) = in_flight_ops_summary.ready_op_index {
#[hdl]
if let HdlSome(in_flight_op) = in_flight_ops[ready_op_index] {
connect(
execute_start.data,
HdlSome(
#[hdl]
ExecuteStart::<_, _> {
insn: in_flight_op.insn,
src_values: read_src_values,
},
),
);
}
}
connect(
unit_to_reg_alloc.output,
Expr::ty(unit_to_reg_alloc.output).HdlNone(),
);
#[hdl]
let input_in_flight_op = wire(HdlOption[in_flight_op_ty]);
connect(input_in_flight_op, HdlOption[in_flight_op_ty].HdlNone());
#[hdl]
if let HdlSome(input) = ReadyValid::firing_data(unit_to_reg_alloc.input) {
#[hdl]
let RenamedInsnData::<_, _, _> {
retire_queue_index,
pc: _,
dest: _,
mop,
} = input;
#[hdl]
let input_mop_src_regs = wire(mop_ty.src_regs_ty());
connect(
input_mop_src_regs,
repeat(PRegNum[config].const_zero().cast_to_bits(), ConstUsize),
);
MOp::connect_src_regs(mop, input_mop_src_regs);
let src_ready_flags = wire_with_loc(
"input_in_flight_op_src_ready_flags",
SourceLocation::caller(),
StaticType::TYPE,
);
connect(src_ready_flags, input_src_regs_valid);
connect(input_src_regs, input_mop_src_regs);
#[hdl]
let input_is_canceled = wire();
connect(input_is_canceled, false);
#[hdl]
if let HdlSome(cancel_input) = unit_to_reg_alloc.cancel_input {
connect(
input_is_canceled,
UnitCancelInput::is_canceled(cancel_input, retire_queue_index),
);
}
#[hdl]
if !input_is_canceled {
connect(
input_in_flight_op,
HdlSome(
#[hdl]
InFlightOp::<_, _> {
state: InFlightOpState.Ready(),
insn: input,
src_ready_flags,
},
),
);
}
#[hdl]
if let HdlSome(empty_op_index) = in_flight_ops_summary.empty_op_index {
connect(in_flight_ops[empty_op_index], input_in_flight_op);
}
}
#[hdl]
let in_flight_op_next_state = wire(Array[HdlOption[InFlightOpState]][max_in_flight]);
#[hdl]
let in_flight_op_next_src_ready_flags =
wire(Array[in_flight_op_ty.src_ready_flags][max_in_flight]);
#[hdl]
let in_flight_op_canceling = wire(Array[Bool][max_in_flight]);
#[hdl]
let in_flight_op_execute_starting = wire(Array[Bool][max_in_flight]);
#[hdl]
let in_flight_op_execute_ending = wire(Array[Bool][max_in_flight]);
for in_flight_op_index in 0..max_in_flight {
connect(
in_flight_op_next_src_ready_flags[in_flight_op_index],
[false; COMMON_MOP_SRC_LEN],
);
connect(in_flight_op_canceling[in_flight_op_index], false);
connect(in_flight_op_execute_starting[in_flight_op_index], false);
connect(in_flight_op_execute_ending[in_flight_op_index], false);
#[hdl]
if let HdlSome(in_flight_op) = in_flight_ops[in_flight_op_index] {
#[hdl]
let InFlightOp::<_, _> {
state,
insn,
src_ready_flags,
} = in_flight_op;
let src_regs = wire_with_loc(
&format!("in_flight_op_src_regs_{in_flight_op_index}"),
SourceLocation::caller(),
mop_ty.src_regs_ty(),
);
connect(
src_regs,
repeat(PRegNum[config].const_zero().cast_to_bits(), ConstUsize),
);
MOp::connect_src_regs(insn.mop, src_regs);
#[hdl]
if in_flight_ops_summary.ready_op_index.cmp_eq(HdlSome(
in_flight_op_index.cast_to(Expr::ty(in_flight_ops_summary).ready_op_index.HdlSome),
)) {
connect(read_src_regs, src_regs);
}
connect(
in_flight_op_next_src_ready_flags[in_flight_op_index],
src_ready_flags,
);
for unit_index in 0..Expr::ty(unit_output_writes).len() {
#[hdl]
if let HdlSome(unit_output_write) = unit_output_writes[unit_index] {
#[hdl]
let UnitOutputWrite::<_> {
dest: unit_out_reg,
value: _,
} = unit_output_write;
let p_reg_num = #[hdl]
PRegNum::<_> {
unit_num: UnitNum[config].from_index(unit_index),
unit_out_reg,
};
for src_index in 0..COMMON_MOP_SRC_LEN {
#[hdl]
if p_reg_num.cast_to_bits().cmp_eq(src_regs[src_index]) {
connect(
in_flight_op_next_src_ready_flags[in_flight_op_index][src_index],
true,
);
}
}
}
}
connect(in_flight_op_canceling[in_flight_op_index], false);
#[hdl]
if let HdlSome(cancel_input) = unit_to_reg_alloc.cancel_input {
connect(
in_flight_op_canceling[in_flight_op_index],
UnitCancelInput::is_canceled(cancel_input, insn.retire_queue_index),
);
}
#[hdl]
if let HdlSome(execute_end) = execute_end {
#[hdl]
let ExecuteEnd::<_, _> { unit_output } = execute_end;
#[hdl]
if insn.dest.cmp_eq(unit_output.dest) {
connect(in_flight_op_execute_ending[in_flight_op_index], true);
#[hdl]
if !in_flight_op_canceling[in_flight_op_index] {
#[hdl]
match state {
InFlightOpState::Running | InFlightOpState::Ready => {
connect(unit_to_reg_alloc.output, HdlSome(unit_output))
}
InFlightOpState::CanceledAndRunning => {}
}
}
}
}
#[hdl]
if let HdlSome(execute_start) = ReadyValid::firing_data(execute_start) {
#[hdl]
if insn.dest.cmp_eq(execute_start.insn.dest) {
connect(in_flight_op_execute_starting[in_flight_op_index], true);
}
}
let connect_next_state = |f| {
InFlightOpState::connect_next_state(
in_flight_op_canceling[in_flight_op_index],
in_flight_op_execute_starting[in_flight_op_index],
in_flight_op_execute_ending[in_flight_op_index],
f,
in_flight_op_next_state[in_flight_op_index],
);
};
#[hdl]
match state {
InFlightOpState::Ready => connect_next_state(InFlightOpState::ready_next_state),
InFlightOpState::Running => connect_next_state(InFlightOpState::running_next_state),
InFlightOpState::CanceledAndRunning => {
connect_next_state(InFlightOpState::canceled_and_running_next_state);
}
}
#[hdl]
if let HdlSome(state) = in_flight_op_next_state[in_flight_op_index] {
connect(
in_flight_ops[in_flight_op_index],
HdlSome(
#[hdl]
InFlightOp::<_, _> {
state,
insn,
src_ready_flags: in_flight_op_next_src_ready_flags[in_flight_op_index],
},
),
);
} else {
connect(
in_flight_ops[in_flight_op_index],
HdlOption[in_flight_op_ty].HdlNone(),
);
}
} else {
connect(in_flight_op_next_state[in_flight_op_index], HdlNone());
}
}
}

View file

@ -1,18 +1,7 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use fayalite::{
bundle::BundleType, expr::ops::ArrayLiteral, module::wire_with_loc, prelude::*,
sim::vcd::VcdWriterDecls, util::RcWriter,
};
use std::{
num::NonZero,
panic::Location,
path::{Path, PathBuf},
};
pub mod array_vec;
pub mod tree_reduce;
pub(crate) const fn range_u32_len(range: &std::ops::Range<u32>) -> usize {
let retval = range.end.saturating_sub(range.start);
@ -36,433 +25,15 @@ pub(crate) const fn range_u32_nth_or_panic(range: &std::ops::Range<u32>, index:
}
}
// TODO: move to fayalite
pub trait Rotate<Amount> {
type Output;
/// like [`usize::rotate_left`] or [`<[T]>::rotate_left`](slice::rotate_left) depending on `Self` -- note that in lsb0 those rotate in opposite directions
fn rotate_left(&self, amount: Amount) -> Self::Output;
/// like [`usize::rotate_right`] or [`<[T]>::rotate_right`](slice::rotate_right) depending on `Self` -- note that in lsb0 those rotate in opposite directions
fn rotate_right(&self, amount: Amount) -> Self::Output;
}
impl<VSz: Size> Rotate<usize> for Expr<UIntType<VSz>> {
type Output = Self;
/// like [`usize::rotate_left`]
fn rotate_left(&self, amount: usize) -> Self::Output {
if self.ty().width() == 0 {
return *self;
}
let amount = amount % self.ty().width();
let l = *self << amount;
let r = *self >> (self.ty().width() - amount);
(l | r).cast_to(self.ty())
}
/// like [`usize::rotate_right`]
fn rotate_right(&self, amount: usize) -> Self::Output {
if self.ty().width() == 0 {
return *self;
}
let amount = amount % self.ty().width();
let l = *self << (self.ty().width() - amount);
let r = *self >> amount;
(l | r).cast_to(self.ty())
pub(crate) const fn range_intersection(
a: &std::ops::Range<u32>,
b: &std::ops::Range<u32>,
) -> Option<std::ops::Range<u32>> {
let start = if a.start > b.start { a.start } else { b.start };
let end = if a.end < b.end { a.end } else { b.end };
if start < end {
Some(start..end)
} else {
None
}
}
impl<VSz: Size> Rotate<usize> for SimValue<UIntType<VSz>> {
type Output = Self;
/// like [`usize::rotate_left`]
fn rotate_left(&self, amount: usize) -> Self::Output {
if self.ty().width() == 0 {
return self.clone();
}
let amount = amount % self.ty().width();
let l = self << amount;
let r = self >> (self.ty().width() - amount);
(l | r).cast_to(self.ty())
}
/// like [`usize::rotate_right`]
fn rotate_right(&self, amount: usize) -> Self::Output {
if self.ty().width() == 0 {
return self.clone();
}
let amount = amount % self.ty().width();
let l = self << (self.ty().width() - amount);
let r = self >> amount;
(l | r).cast_to(self.ty())
}
}
impl<VSz: Size, ASz: Size> Rotate<Expr<UIntType<ASz>>> for Expr<UIntType<VSz>> {
type Output = Self;
/// like [`usize::rotate_left`]
fn rotate_left(&self, amount: Expr<UIntType<ASz>>) -> Self::Output {
if self.ty().width() == 0 {
return *self;
}
let amount = amount % self.ty().width();
let l = *self << amount;
let r = *self >> (self.ty().width() - amount);
(l | r).cast_to(self.ty())
}
/// like [`usize::rotate_right`]
fn rotate_right(&self, amount: Expr<UIntType<ASz>>) -> Self::Output {
if self.ty().width() == 0 {
return *self;
}
let amount = amount % self.ty().width();
let l = *self << (self.ty().width() - amount).cast_to(amount.ty());
let r = *self >> amount;
(l | r).cast_to(self.ty())
}
}
impl<VSz: Size, ASz: Size> Rotate<SimValue<UIntType<ASz>>> for SimValue<UIntType<VSz>> {
type Output = Self;
/// like [`usize::rotate_left`]
fn rotate_left(&self, amount: SimValue<UIntType<ASz>>) -> Self::Output {
if self.ty().width() == 0 {
return self.clone();
}
let amount = amount % self.ty().width();
let l = self << &amount;
let r = self >> (self.ty().width() - amount);
(l | r).cast_to(self.ty())
}
/// like [`usize::rotate_right`]
fn rotate_right(&self, amount: SimValue<UIntType<ASz>>) -> Self::Output {
if self.ty().width() == 0 {
return self.clone();
}
let amount = amount % self.ty().width();
let l = self << (self.ty().width() - &amount).cast_to(amount.ty());
let r = self >> amount;
(l | r).cast_to(self.ty())
}
}
fn array_rotate_helper<T: Type, N: Size, AmountSize: Size>(
mut array: Expr<ArrayType<T, N>>,
amount: Expr<UIntType<AmountSize>>,
rotate_fn: impl Fn(&mut [Expr<T>], usize),
rotate_fn_name: &str,
) -> Expr<ArrayType<T, N>> {
let Some(mut prev_step_size) = NonZero::new(array.ty().len()) else {
return array;
};
fn named<T: Type>(v: Expr<T>, name: impl AsRef<str>) -> Expr<T> {
let w = wire_with_loc(name.as_ref(), SourceLocation::caller(), v.ty());
connect(w, v);
w
}
fn non_empty_array_to_expr<T: Type, Len: Size>(
v: impl AsRef<[Expr<T>]>,
) -> Expr<ArrayType<T, Len>> {
let v = v.as_ref();
ArrayLiteral::new(v[0].ty(), v.iter().map(|v| Expr::canonical(*v)).collect()).to_expr()
}
fn mux<T: Type>(b: Expr<Bool>, true_v: Expr<T>, false_v: Expr<T>) -> Expr<T> {
let a: Expr<Array<T, 2>> = non_empty_array_to_expr([false_v, true_v]);
a[b.cast_to_static::<UInt<1>>()]
}
let amount_ty = amount.ty();
let mut amount = (amount % prev_step_size).cast_to(amount_ty);
loop {
(prev_step_size, amount, array) =
if let Some(step_size) = NonZero::new(prev_step_size.get() / 2) {
let amount = named(amount, format!("{rotate_fn_name}_amount_{prev_step_size}"));
let do_rotate = amount.cmp_ge(step_size);
let mut rotated_array = (*array).clone();
rotate_fn(rotated_array.as_mut(), step_size.get());
let rotated_array = named(
non_empty_array_to_expr(rotated_array),
format!("{rotate_fn_name}_rotated_array_{step_size}"),
);
let array = mux(do_rotate, rotated_array, array);
let array = named(array, format!("{rotate_fn_name}_array_{step_size}"));
let amount = mux(do_rotate, (amount - step_size).cast_to(amount_ty), amount);
(step_size, amount, array)
} else {
return array;
};
}
}
impl<T: Type, N: Size, AmountSize: Size> Rotate<Expr<UIntType<AmountSize>>>
for Expr<ArrayType<T, N>>
{
type Output = Self;
/// like [`<[T]>::rotate_left`](slice::rotate_left)
fn rotate_left(&self, amount: Expr<UIntType<AmountSize>>) -> Self::Output {
array_rotate_helper(*self, amount, <[Expr<T>]>::rotate_left, "rotate_left")
}
/// like [`<[T]>::rotate_right`](slice::rotate_right)
fn rotate_right(&self, amount: Expr<UIntType<AmountSize>>) -> Self::Output {
array_rotate_helper(*self, amount, <[Expr<T>]>::rotate_right, "rotate_right")
}
}
impl<T: Type, N: Size, AmountSize: Size> Rotate<SimValue<UIntType<AmountSize>>>
for SimValue<ArrayType<T, N>>
{
type Output = Self;
/// like [`<[T]>::rotate_left`](slice::rotate_left)
fn rotate_left(&self, amount: SimValue<UIntType<AmountSize>>) -> Self::Output {
if self.ty().len() == 0 {
return self.clone();
}
let Ok(amount) = usize::try_from(amount.to_bigint() % self.ty().len()) else {
unreachable!();
};
let mut retval = self.clone();
AsMut::<[SimValue<T>]>::as_mut(&mut SimValue::value_mut(&mut retval)).rotate_left(amount);
retval
}
/// like [`<[T]>::rotate_right`](slice::rotate_right)
fn rotate_right(&self, amount: SimValue<UIntType<AmountSize>>) -> Self::Output {
if self.ty().len() == 0 {
return self.clone();
}
let Ok(amount) = usize::try_from(amount.to_bigint() % self.ty().len()) else {
unreachable!();
};
let mut retval = self.clone();
AsMut::<[SimValue<T>]>::as_mut(&mut SimValue::value_mut(&mut retval)).rotate_right(amount);
retval
}
}
impl<T: Type, N: Size> Rotate<usize> for Expr<ArrayType<T, N>> {
type Output = Self;
/// like [`<[T]>::rotate_left`](slice::rotate_left)
fn rotate_left(&self, amount: usize) -> Self::Output {
if self.ty().len() == 0 {
return self.clone();
}
let amount = amount % self.ty().len();
let mut retval = Vec::from_iter(*self);
retval.rotate_left(amount);
ArrayLiteral::new(
self.ty().element(),
retval.into_iter().map(Expr::canonical).collect(),
)
.to_expr()
}
/// like [`<[T]>::rotate_right`](slice::rotate_right)
fn rotate_right(&self, amount: usize) -> Self::Output {
if self.ty().len() == 0 {
return self.clone();
}
let amount = amount % self.ty().len();
let mut retval = Vec::from_iter(*self);
retval.rotate_right(amount);
ArrayLiteral::new(
self.ty().element(),
retval.into_iter().map(Expr::canonical).collect(),
)
.to_expr()
}
}
impl<T: Type, N: Size> Rotate<usize> for SimValue<ArrayType<T, N>> {
type Output = Self;
/// like [`<[T]>::rotate_left`](slice::rotate_left)
fn rotate_left(&self, amount: usize) -> Self::Output {
if self.ty().len() == 0 {
return self.clone();
}
let amount = amount % self.ty().len();
let mut retval = self.clone();
AsMut::<[SimValue<T>]>::as_mut(&mut SimValue::value_mut(&mut retval)).rotate_left(amount);
retval
}
/// like [`<[T]>::rotate_right`](slice::rotate_right)
fn rotate_right(&self, amount: usize) -> Self::Output {
if self.ty().len() == 0 {
return self.clone();
}
let amount = amount % self.ty().len();
let mut retval = self.clone();
AsMut::<[SimValue<T>]>::as_mut(&mut SimValue::value_mut(&mut retval)).rotate_right(amount);
retval
}
}
#[hdl]
pub struct LFSR31 {
// MSB is always zero, 32 bits makes it easier to manipulate
state: UInt<32>,
}
impl LFSR31 {
#[hdl]
pub fn new() -> SimValue<Self> {
#[hdl(sim)]
Self { state: 1u32 }
}
}
impl LFSR31 {
pub fn next_sim(this: &mut SimValue<Self>) -> u32 {
let state = this.state.as_int();
let state = if state == 0 {
1u32
} else {
// a maximal-length 31-bit LFSR
let lsb = ((state >> 30) ^ (state >> 27)) & 1;
let msb = (state << 1) & ((1 << 31) - 1);
lsb | msb
};
*this.state = state.into();
state
}
}
pub struct CheckedVcdOutput {
writer: Option<RcWriter>,
expected_path: PathBuf,
expected_contents: Result<String, (Option<PathBuf>, std::io::Error)>,
location: &'static Location<'static>,
}
impl CheckedVcdOutput {
#[must_use]
#[track_caller]
pub fn new<T: BundleType>(sim: &mut Simulation<T>, expected_path: PathBuf) -> Self {
let writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
Self {
writer: Some(writer),
expected_contents: std::fs::read_to_string(&expected_path).map_err(|e| {
eprintln!(
"error: failed to read expected VCD from: {}",
expected_path.display(),
);
(std::env::current_dir().ok(), e)
}),
expected_path,
location: Location::caller(),
}
}
#[must_use]
#[track_caller]
#[doc(hidden)]
pub fn __checked_vcd_output_macro_helper<T: BundleType>(
sim: &mut Simulation<T>,
cargo_manifest_dir: &'static str,
path: &'static str,
) -> Self {
Self::new(sim, Path::new(cargo_manifest_dir).join(path))
}
pub fn with_vcd_output<R>(&self, f: impl FnOnce(&str) -> R) -> R {
let Some(writer) = &self.writer else {
unreachable!();
};
writer.clone().borrow(|output| {
let Ok(output) = str::from_utf8(output) else {
unreachable!("VcdWriter writes valid UTF-8");
};
f(output)
})
}
#[track_caller]
pub fn finish(mut self) {
let Ok(()) = self.finish_impl(|msg| panic!("{msg}"));
}
fn finish_impl<E>(
&mut self,
error: impl FnOnce(std::fmt::Arguments<'_>) -> E,
) -> Result<(), E> {
let Self {
writer: Some(writer),
expected_path,
expected_contents,
location,
} = self
else {
// already finished
return Ok(());
};
let Ok(vcd) = String::from_utf8(writer.take()) else {
unreachable!("VcdWriter writes valid UTF-8");
};
let expected_path_d = expected_path.display();
if expected_contents
.as_ref()
.is_ok_and(|expected_contents| *expected_contents == vcd)
{
// avoid written output from being split from threads interleaving writes to stdout
let _stdout = std::io::stderr().lock();
// use println to get output captured by tests
println!("\n{location}: generated VCD matches the expected VCD in {expected_path_d}");
return Ok(());
}
// avoid written output from being split from threads interleaving writes to stderr
let _stderr = std::io::stderr().lock();
let error = |msg: std::fmt::Arguments<'_>| {
// print msg at both beginning and end so it's easier to find when the vcd is huge
Err(error(format_args!(
"\n{msg}####### VCD:\n{vcd}\n#######\n{msg}"
)))
};
let error = |msg: std::fmt::Arguments<'_>| match &*expected_contents {
Ok(_) => error(format_args!(
"{location}: generated VCD doesn't match the expected VCD in {expected_path_d}\n\
{msg}",
)),
Err((Some(current_dir), e)) => error(format_args!(
"{location}: generated VCD doesn't match the expected VCD in {expected_path_d}\n\
error: failed to read: {e}\n\
current dir: {current_dir}\n\
{msg}",
current_dir = current_dir.display(),
)),
Err((None, e)) => error(format_args!(
"{location}: generated VCD doesn't match the expected VCD in {expected_path_d}\n\
error: failed to read: {e}\n\
{msg}",
)),
};
const OVERWRITE_VAR_NAME: &str = "OVERWRITE_EXPECTED_VCD";
const OVERWRITE_VAR_VALUE: &str = "overwrite";
match std::env::var_os(OVERWRITE_VAR_NAME) {
Some(v) if v == OVERWRITE_VAR_VALUE => match std::fs::write(&expected_path, &vcd) {
Ok(()) => error(format_args!(
"warning: since `{OVERWRITE_VAR_NAME}={OVERWRITE_VAR_VALUE}` is set -- writing the generated VCD to {expected_path_d}\n"
)),
Err(e) => error(format_args!(
"error: since `{OVERWRITE_VAR_NAME}={OVERWRITE_VAR_VALUE}` is set -- tried to write the generated VCD to {expected_path_d}\n\
error: failed to write: {e}"
)),
},
_ => error(format_args!(
"note: rerun the test with the environment variable `{OVERWRITE_VAR_NAME}={OVERWRITE_VAR_VALUE}`\n\
to update the expected output to match the generated output.\n"
)),
}
}
}
impl Drop for CheckedVcdOutput {
#[track_caller]
fn drop(&mut self) {
let _ = self.finish_impl(|msg| {
if std::thread::panicking() {
eprintln!("{msg}"); // use eprintln to get output captured by tests
} else {
panic!("{msg}");
}
});
}
}
#[macro_export]
macro_rules! checked_vcd_output {
($sim:expr, $path_relative_to_manifest_dir:expr $(,)?) => {
$crate::util::CheckedVcdOutput::__checked_vcd_output_macro_helper(
$sim,
::std::env!("CARGO_MANIFEST_DIR"),
::std::concat!($path_relative_to_manifest_dir),
)
};
}

View file

@ -1,28 +1,150 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use fayalite::{expr::ops::ExprIndex, int::UIntInRangeInclusiveType, prelude::*};
use std::fmt;
use fayalite::{
expr::{
ops::{ExprCastTo, ExprIndex, ExprPartialEq, ExprPartialOrd},
ToLiteralBits,
},
int::{IntType, SizeType},
intern::{Intern, Interned},
prelude::*,
ty::{MatchVariantWithoutScope, StaticType, TypeProperties},
};
use std::{marker::PhantomData, ops::Index};
#[derive(Clone, Debug)]
pub struct ArrayVecFullError<V, I: Iterator> {
pub value: V,
pub rest: std::iter::Chain<std::iter::Once<I::Item>, I>,
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct Length<Max: Size> {
ty: UInt,
_phantom: PhantomData<Max>,
}
impl<V, I: Iterator> fmt::Display for ArrayVecFullError<V, I> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ArrayVec is full")
impl<Max: Size> Length<Max> {
pub fn new(max: Max::SizeType) -> Self {
Self {
ty: UInt::range_inclusive(0..=Max::as_usize(max)),
_phantom: PhantomData,
}
}
pub fn ty(self) -> UInt {
self.ty
}
pub fn zero(self) -> Expr<Self> {
Self::from_uint_unchecked(self.ty.zero())
}
pub fn from_uint_unchecked(v: impl ToExpr<Type = UInt>) -> Expr<Self> {
Expr::from_canonical(Expr::canonical(v.to_expr()))
}
pub fn cast_from_uint_unchecked<SrcWidth: Size>(
self,
v: impl ToExpr<Type = UIntType<SrcWidth>>,
) -> Expr<Self> {
Self::from_uint_unchecked(v.to_expr().cast_to(self.ty))
}
pub fn as_uint(this: impl ToExpr<Type = Self>) -> Expr<UInt> {
let this = this.to_expr();
this.cast_to(Expr::ty(this).ty)
}
}
impl<V: fmt::Debug, I: Iterator<Item: fmt::Debug> + fmt::Debug> std::error::Error
for ArrayVecFullError<V, I>
{
impl<Max: Size, DestWidth: Size> ExprCastTo<UIntType<DestWidth>> for Length<Max> {
fn cast_to(src: Expr<Self>, to_type: UIntType<DestWidth>) -> Expr<UIntType<DestWidth>> {
Expr::<UInt>::from_canonical(Expr::canonical(src)).cast_to(to_type)
}
}
#[hdl]
pub type Length<Max: Size> = UIntInRangeInclusiveType<ConstUsize<0>, Max>;
#[allow(non_upper_case_globals)]
pub const Length: __LengthWithoutGenerics = __LengthWithoutGenerics {};
#[non_exhaustive]
pub struct __LengthWithoutGenerics {}
impl<M: SizeType> Index<M> for __LengthWithoutGenerics {
type Output = Length<M::Size>;
fn index(&self, max: M) -> &Self::Output {
Interned::into_inner(Length::new(max).intern_sized())
}
}
impl<Max: Size> Type for Length<Max> {
type BaseType = UInt;
type MaskType = Bool;
type MatchVariant = Expr<Self>;
type MatchActiveScope = ();
type MatchVariantAndInactiveScope = MatchVariantWithoutScope<Self::MatchVariant>;
type MatchVariantsIter = std::iter::Once<Self::MatchVariantAndInactiveScope>;
fn match_variants(
this: Expr<Self>,
source_location: SourceLocation,
) -> Self::MatchVariantsIter {
let _ = source_location;
std::iter::once(MatchVariantWithoutScope(this))
}
fn mask_type(&self) -> Self::MaskType {
Bool
}
fn canonical(&self) -> CanonicalType {
self.ty.canonical()
}
fn from_canonical(canonical_type: CanonicalType) -> Self {
let ty = <UInt>::from_canonical(canonical_type);
if let Some(known_max) = Max::KNOWN_VALUE {
assert_eq!(ty, UInt::range_inclusive(0..=known_max));
}
Self {
ty,
_phantom: PhantomData,
}
}
fn source_location() -> SourceLocation {
SourceLocation::caller()
}
}
impl<Max: KnownSize> StaticType for Length<Max> {
const TYPE: Self = Self {
ty: UInt {
width: Max::VALUE.next_power_of_two().ilog2() as usize,
},
_phantom: PhantomData,
};
const MASK_TYPE: Self::MaskType = Bool;
const TYPE_PROPERTIES: TypeProperties = {
let mut p = <UInt<1>>::TYPE_PROPERTIES;
p.bit_width = Self::TYPE.ty.width;
p
};
const MASK_TYPE_PROPERTIES: TypeProperties = Bool::TYPE_PROPERTIES;
}
impl<Max: Size> ExprPartialEq<Self> for Length<Max> {
fn cmp_eq(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
Self::as_uint(lhs).cmp_eq(Self::as_uint(rhs))
}
fn cmp_ne(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
Self::as_uint(lhs).cmp_ne(Self::as_uint(rhs))
}
}
impl<Max: Size> ExprPartialOrd<Self> for Length<Max> {
fn cmp_lt(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
Self::as_uint(lhs).cmp_lt(Self::as_uint(rhs))
}
fn cmp_le(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
Self::as_uint(lhs).cmp_le(Self::as_uint(rhs))
}
fn cmp_gt(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
Self::as_uint(lhs).cmp_gt(Self::as_uint(rhs))
}
fn cmp_ge(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
Self::as_uint(lhs).cmp_ge(Self::as_uint(rhs))
}
}
/// like [`std::vec::Vec`], except with a [`Expr`] for [`len()`][`Self::len()`] and a fixed capacity
#[hdl]
@ -37,55 +159,7 @@ impl<T: Type, N: Size> ArrayVec<T, N> {
#[hdl]
ArrayVec {
elements: self.elements.uninit(),
len: 0u8.cast_to(self.len),
}
}
#[hdl]
pub fn new_sim(self, uninit_element: impl ToSimValueWithType<T>) -> SimValue<Self> {
let uninit_element = uninit_element.into_sim_value_with_type(self.element());
#[hdl(sim)]
ArrayVec::<_, _> {
elements: SimValue::from_array_elements(
self.elements,
(0..self.elements.len()).map(|_| uninit_element.clone()),
),
len: 0u8.cast_to(self.len),
}
}
#[hdl]
pub fn new_full_sim(
self,
elements: impl ToSimValueWithType<ArrayType<T, N>>,
) -> SimValue<Self> {
let elements = elements.to_sim_value_with_type(self.elements);
#[hdl(sim)]
Self {
elements,
len: self.elements.len().to_sim_value_with_type(self.len),
}
}
pub fn from_iter_sim<I: IntoIterator<Item: ToSimValueWithType<T>>>(
self,
uninit_element: impl ToSimValueWithType<T>,
iter: I,
) -> Result<SimValue<Self>, ArrayVecFullError<SimValue<Self>, I::IntoIter>> {
let mut value = Self::new_sim(self, uninit_element);
let element = self.element();
let mut iter = iter.into_iter();
for i in 0..self.capacity() {
let Some(v) = iter.next() else {
break;
};
value.elements[i] = v.into_sim_value_with_type(element);
*value.len = i + 1;
}
if let Some(extra) = iter.next() {
Err(ArrayVecFullError {
value,
rest: std::iter::once(extra).chain(iter),
})
} else {
Ok(value)
len: self.len.zero(),
}
}
pub fn element(self) -> T {
@ -105,8 +179,8 @@ impl<T: Type, N: Size> ArrayVec<T, N> {
let elements = elements.to_expr();
let len = len.to_expr();
assert_eq!(
Length[N::from_usize(elements.ty().len())],
len.ty(),
Length::new(N::from_usize(Expr::ty(elements).len())),
Expr::ty(len),
"len type mismatch",
);
#[hdl]
@ -118,12 +192,9 @@ impl<T: Type, N: Size> ArrayVec<T, N> {
pub fn len(this: impl ToExpr<Type = Self>) -> Expr<Length<N>> {
this.to_expr().len
}
pub fn len_sim(this: &SimValue<Self>) -> &SimValue<Length<N>> {
&this.len
}
pub fn is_empty(this: impl ToExpr<Type = Self>) -> Expr<Bool> {
let len = Self::len(this);
len.cmp_eq(0u8)
len.cmp_eq(Expr::ty(len).zero())
}
pub fn capacity(self) -> usize {
self.elements.len()
@ -139,77 +210,11 @@ impl<T: Type, N: Size> ArrayVec<T, N> {
let this = this.to_expr();
for (index, element) in this.elements.into_iter().enumerate() {
#[hdl]
if index.cmp_lt(this.len) {
if index.cmp_lt(Length::as_uint(this.len)) {
f(index, element);
}
}
}
pub fn elements_sim_ref(this: &SimValue<Self>) -> &[SimValue<T>] {
&this.elements[..*this.len]
}
pub fn elements_sim_mut(this: &mut SimValue<Self>) -> &mut [SimValue<T>] {
let len = *this.len;
&mut this.elements[..len]
}
#[hdl]
pub async fn async_for_each_sim(
this: impl ToSimValue<Type = Self>,
mut f: impl AsyncFnMut(usize, SimValue<T>),
) {
#[hdl(sim)]
let ArrayVec::<_, _> { elements, len } = this.into_sim_value();
for (index, element) in elements.into_iter().enumerate() {
if index.cmp_lt(*len) {
f(index, element).await;
}
}
}
#[hdl]
pub async fn async_for_each_sim_ref<'a>(
this: &'a SimValue<Self>,
mut f: impl AsyncFnMut(usize, &'a SimValue<T>),
) {
#[hdl(sim)]
let ArrayVec::<_, _> { elements, len } = this;
for (index, element) in elements.iter().enumerate() {
if index.cmp_lt(**len) {
f(index, element).await;
}
}
}
#[hdl]
pub async fn async_for_each_sim_mut<'a>(
this: &'a mut SimValue<Self>,
mut f: impl AsyncFnMut(usize, &'a mut SimValue<T>),
) {
#[hdl(sim)]
let ArrayVec::<_, _> { elements, len } = this;
for (index, element) in elements.iter_mut().enumerate() {
if index.cmp_lt(**len) {
f(index, element).await;
}
}
}
#[hdl]
pub fn try_push_sim(
this: &mut SimValue<Self>,
value: impl ToSimValueWithType<T>,
) -> Result<(), SimValue<T>> {
let value = value.into_sim_value_with_type(this.ty().element());
let capacity = this.ty().capacity();
#[hdl(sim)]
let ArrayVec::<_, _> { elements, len } = this;
if **len < capacity {
elements[**len] = value;
**len += 1;
Ok(())
} else {
Err(value)
}
}
pub fn truncate_sim(this: &mut SimValue<Self>, len: usize) {
*this.len = len.min(*this.len);
}
pub fn mapped_ty<U: Type>(self, new_element_ty: U) -> ArrayVec<U, N> {
ArrayVec {
elements: ArrayType[new_element_ty][N::from_usize(self.elements.len())],
@ -224,7 +229,7 @@ impl<T: Type, N: Size> ArrayVec<T, N> {
) -> Expr<ArrayVec<U, N>> {
let this = this.to_expr();
#[hdl]
let mapped_array_vec = wire(this.ty().mapped_ty(new_element_ty));
let mapped_array_vec = wire(Expr::ty(this).mapped_ty(new_element_ty));
connect(mapped_array_vec.len, this.len);
Self::for_each(this, |index, element| {
connect(mapped_array_vec[index], f(index, element));
@ -232,48 +237,44 @@ impl<T: Type, N: Size> ArrayVec<T, N> {
mapped_array_vec
}
#[hdl]
pub fn map_sim<U: Type>(
this: impl ToSimValue<Type = Self>,
uninit_element: impl ToSimValue<Type = U>,
mut f: impl FnMut(usize, SimValue<T>) -> SimValue<U>,
) -> SimValue<ArrayVec<U, N>> {
let this = this.into_sim_value();
let uninit_element = uninit_element.into_sim_value();
let ty = this.ty().mapped_ty(uninit_element.ty());
#[hdl(sim)]
let Self { elements, len } = this;
#[hdl(sim)]
ArrayVec::<_, _> {
elements: SimValue::from_array_elements(
ty.elements,
SimValue::into_value(elements)
.into_iter()
.enumerate()
.map(|(index, element)| {
if index < *len {
f(index, element)
} else {
uninit_element.clone()
}
}),
),
len,
}
}
#[hdl]
pub fn as_array_of_options(this: impl ToExpr<Type = Self>) -> Expr<ArrayType<HdlOption<T>, N>> {
let this = this.to_expr();
#[hdl]
let array_vec_as_array_of_options =
wire(ArrayType[HdlOption[this.ty().element()]][N::from_usize(this.ty().capacity())]);
let array_vec_as_array_of_options = wire(
ArrayType[HdlOption[Expr::ty(this).element()]]
[N::from_usize(Expr::ty(this).capacity())],
);
for element in array_vec_as_array_of_options {
connect(element, element.ty().HdlNone());
connect(element, Expr::ty(element).HdlNone());
}
Self::for_each(this, |index, element| {
connect(array_vec_as_array_of_options[index], HdlSome(element))
});
array_vec_as_array_of_options
}
#[hdl]
pub fn get<Idx: IntType<Dyn = UInt>>(
this: impl ToExpr<Type = Self>,
index: impl ToExpr<Type = Idx>,
) -> Expr<HdlOption<T>> {
let this = this.to_expr();
let index = Expr::as_dyn_int(index.to_expr());
let never_in_bounds = index.cmp_ge(Expr::ty(this).capacity());
if let Ok(never_in_bounds) = never_in_bounds.to_literal_bits() {
if never_in_bounds[0] {
// avoid error from out-of-bounds constant index
return HdlOption[Expr::ty(this).element()].HdlNone();
}
}
#[hdl]
let array_vec_get = wire(HdlOption[Expr::ty(this).element()]);
connect(array_vec_get, Expr::ty(array_vec_get).HdlNone());
#[hdl]
if index.cmp_lt(Length::as_uint(Self::len(this))) {
connect(array_vec_get, HdlSome(this.elements[index]));
}
array_vec_get
}
}
impl<T: Type, N: Size, Idx, IdxWidth: Size> ExprIndex<Idx> for ArrayVec<T, N>
@ -289,33 +290,34 @@ where
}
}
impl<T: Type> ArrayVec<T, ConstUsize<1>> {
#[hdl]
pub struct ReadyValidArray<T: Type, N: Size> {
pub data: ArrayVec<T, N>,
#[hdl(flip)]
pub ready: Length<N>,
}
impl<T: Type, N: Size> ReadyValidArray<T, N> {
#[hdl]
pub fn from_opt_sim(
opt: impl ToSimValue<Type = HdlOption<T>>,
uninit_element: impl ToSimValueWithType<T>,
) -> SimValue<Self> {
let opt = opt.into_sim_value();
let ty = ArrayVec[opt.ty().HdlSome][ConstUsize];
#[hdl(sim)]
match opt {
HdlSome(v) => ty.new_full_sim([v]),
HdlNone => ty.new_sim(uninit_element),
pub fn firing_len(this: impl ToExpr<Type = Self>) -> Expr<Length<N>> {
let this = this.to_expr();
assert_eq!(Expr::ty(this).data.len_ty(), Expr::ty(this).ready);
#[hdl]
let firing_len = wire(Expr::ty(this).data.len);
connect(firing_len, this.data.len);
#[hdl]
if this.data.len.cmp_gt(this.ready) {
connect(firing_len, this.ready);
}
firing_len
}
#[hdl]
pub fn into_opt_sim(this: impl ToSimValue<Type = Self>) -> SimValue<HdlOption<T>> {
let this = this.into_sim_value();
#[hdl(sim)]
let Self { elements, len } = this;
let [element] = SimValue::into_value(elements);
let ty = HdlOption[element.ty()];
if *len == 0 {
#[hdl(sim)]
ty.HdlNone()
} else {
#[hdl(sim)]
ty.HdlSome(element)
}
pub fn firing_data(this: impl ToExpr<Type = Self>) -> Expr<ArrayVec<T, N>> {
let this = this.to_expr();
#[hdl]
let firing_data = wire(Expr::ty(this).data);
connect(firing_data, this.data);
connect(firing_data.len, Self::firing_len(this));
firing_data
}
}

View file

@ -1,152 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum TreeReduceOp {
Input,
Reduce,
}
#[derive(Copy, Clone, Debug)]
struct Entry {
start: usize,
depth: u32,
}
#[derive(Clone, Debug)]
pub struct TreeReduceOps {
len: usize,
stack: Vec<Entry>,
}
impl TreeReduceOps {
pub fn new(len: usize) -> Self {
TreeReduceOps {
len,
stack: Vec::new(),
}
}
}
impl Iterator for TreeReduceOps {
type Item = TreeReduceOp;
fn next(&mut self) -> Option<Self::Item> {
match *self.stack {
[] if self.len != 0 => {
self.stack.push(Entry { start: 0, depth: 0 });
Some(TreeReduceOp::Input)
}
[.., ref mut second_last, last] if second_last.depth == last.depth => {
second_last.depth += 1;
self.stack.pop();
Some(TreeReduceOp::Reduce)
}
[.., last] if self.len - last.start > 1 << last.depth => {
let start = last.start + (1 << last.depth);
self.stack.push(Entry { start, depth: 0 });
Some(TreeReduceOp::Input)
}
[.., ref mut second_last, _] => {
second_last.depth += 1;
self.stack.pop();
Some(TreeReduceOp::Reduce)
}
_ => None,
}
}
}
#[track_caller]
pub fn tree_reduce_with_state<S, I, R>(
iter: impl IntoIterator<IntoIter: ExactSizeIterator, Item = I>,
state: &mut S,
mut input: impl FnMut(&mut S, I) -> R,
mut reduce: impl FnMut(&mut S, R, R) -> R,
) -> Option<R> {
let mut stack = Vec::new();
let mut iter = iter.into_iter();
for op in TreeReduceOps::new(iter.len()) {
match op {
TreeReduceOp::Input => stack.push(input(
state,
iter.next().expect("inconsistent iterator len() and next()"),
)),
TreeReduceOp::Reduce => {
let Some(r) = stack.pop() else {
unreachable!();
};
let Some(l) = stack.pop() else {
unreachable!();
};
stack.push(reduce(state, l, r));
}
}
}
stack.pop()
}
pub fn tree_reduce<T>(
iter: impl IntoIterator<Item = T, IntoIter: ExactSizeIterator>,
mut reduce: impl FnMut(T, T) -> T,
) -> Option<T> {
tree_reduce_with_state(iter, &mut (), |_, v| v, move |_, l, r| reduce(l, r))
}
#[cfg(test)]
mod tests {
use super::*;
use std::ops::Range;
fn recursive_tree_reduce(range: Range<usize>, ops: &mut Vec<TreeReduceOp>) {
if range.len() == 1 {
ops.push(TreeReduceOp::Input);
return;
}
if range.is_empty() {
return;
}
let pow2_len = range.len().next_power_of_two();
let split = range.start + pow2_len / 2;
recursive_tree_reduce(range.start..split, ops);
recursive_tree_reduce(split..range.end, ops);
ops.push(TreeReduceOp::Reduce);
}
#[test]
fn test_tree_reduce() {
const EXPECTED: &'static [&'static [TreeReduceOp]] = {
use TreeReduceOp::{Input as I, Reduce as R};
&[
&[],
&[I],
&[I, I, R],
&[I, I, R, I, R],
&[I, I, R, I, I, R, R],
&[I, I, R, I, I, R, R, I, R],
&[I, I, R, I, I, R, R, I, I, R, R],
&[I, I, R, I, I, R, R, I, I, R, I, R, R],
&[I, I, R, I, I, R, R, I, I, R, I, I, R, R, R],
]
};
for len in 0..64 {
let mut expected = vec![];
recursive_tree_reduce(0..len, &mut expected);
if let Some(&expected2) = EXPECTED.get(len) {
assert_eq!(*expected, *expected2, "len={len}");
}
assert_eq!(
TreeReduceOps::new(len).collect::<Vec<_>>(),
expected,
"len={len}"
);
let seq: Vec<_> = (0..len).collect();
assert_eq!(
seq,
tree_reduce(seq.iter().map(|&v| vec![v]), |mut l, r| {
l.extend_from_slice(&r);
l
})
.unwrap_or_default()
);
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,707 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use cpu::{
config::{CpuConfig, UnitConfig},
fetch::{FetchToDecodeInterface, FetchToDecodeInterfaceInner, fetch},
main_memory_and_io::{
CpuConfigFetchMemoryInterfaceConfig, MemoryInterface, MemoryInterfaceConfig,
MemoryOperationErrorKind, MemoryOperationFinish, MemoryOperationFinishKind,
MemoryOperationKind, MemoryOperationStart,
},
next_pc::{FETCH_BLOCK_ID_WIDTH, NextPcToFetchInterface, NextPcToFetchInterfaceInner},
unit::UnitKind,
util::array_vec::ArrayVec,
};
use fayalite::{
prelude::*,
sim::vcd::VcdWriterDecls,
util::{DebugAsDisplay, RcWriter},
};
use std::{cell::RefCell, collections::VecDeque, fmt, num::NonZeroUsize};
struct Random {
index: u64,
}
impl Random {
fn next(&mut self) -> u64 {
let index = self.index;
self.index = self.index.wrapping_add(1);
// make a pseudo-random number deterministically based on index
index
.wrapping_add(1)
.wrapping_mul(0x18C49126EABE7A0D) // random prime
.rotate_left(32)
.wrapping_mul(0x92B38C197608A6B) // random prime
.rotate_right(60)
}
}
const MEMORY_QUEUE_SIZE: usize = 16;
#[hdl]
struct MemoryQueueEntry {
addr: UInt<64>,
fetch_block_id: UInt<{ FETCH_BLOCK_ID_WIDTH }>,
cycles_left: UInt<8>,
}
impl MemoryQueueEntry {
#[hdl]
fn default_sim(self) -> SimValue<Self> {
#[hdl(sim)]
Self {
addr: 0u64,
fetch_block_id: self.fetch_block_id.zero(),
cycles_left: 0u8,
}
}
fn get_next_delay(random: &mut Random) -> u8 {
if random.next() % 32 == 0 { 30 } else { 5 }
}
}
const MEMORY_DATA: &str = "Test data, testing...\nTest Test!\nSecond Cache Line\nTesting.....\n";
const MEMORY_START: u64 = 0x1000;
const MEMORY_RANGE2: std::ops::Range<u64> = 0x2000..0x3000;
const MEMORY_ERROR_RANGE: std::ops::Range<u64> = 0x10F00..0x20F00;
const MEMORY_ERROR_STEP: u64 = 0x1000;
fn read_memory(start: u64, len: usize) -> Option<&'static [u8]> {
if MEMORY_ERROR_RANGE.contains(&start) {
let start = start - MEMORY_ERROR_RANGE.start;
let fail_at = start / MEMORY_ERROR_STEP;
let offset = start % MEMORY_ERROR_STEP;
return if offset < fail_at {
[0xFFu8; MEMORY_DATA.len()].get(..len)
} else {
None
};
}
if MEMORY_RANGE2.contains(&start) {
return [0xF2u8; MEMORY_DATA.len()].get(..len);
}
MEMORY_DATA
.as_bytes()
.get(start.checked_sub(MEMORY_START)?.try_into().ok()?..)?
.get(..len)
}
#[hdl_module(extern)]
fn mock_memory(config: PhantomConst<CpuConfig>) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let memory_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
m.input(MemoryInterface[CpuConfigFetchMemoryInterfaceConfig[config]]);
#[hdl]
let queue_debug: ArrayVec<MemoryQueueEntry, ConstUsize<{ MEMORY_QUEUE_SIZE }>> = m.output();
m.register_clock_for_past(cd.clk);
m.extern_module_simulation_fn(
(cd, memory_interface, queue_debug),
|(cd, memory_interface, queue_debug), mut sim| async move {
// intentionally have a different sequence each time we're reset
let random = RefCell::new(Random { index: 0 });
sim.resettable(
cd,
async |mut sim| {
#[hdl]
let MemoryInterface::<_> {
start,
finish,
next_op_ids,
config: _,
} = memory_interface;
sim.write(start.ready, false).await;
sim.write(finish.data, finish.ty().data.HdlNone()).await;
sim.write(next_op_ids, next_op_ids.ty().HdlNone()).await;
sim.write(
queue_debug,
queue_debug.ty().new_sim(MemoryQueueEntry.default_sim()),
)
.await;
},
|sim, ()| run_fn(cd, memory_interface, queue_debug, &random, sim),
)
.await;
},
);
#[hdl]
async fn run_fn(
cd: Expr<ClockDomain>,
memory_interface: Expr<MemoryInterface<PhantomConst<MemoryInterfaceConfig>>>,
queue_debug: Expr<ArrayVec<MemoryQueueEntry, ConstUsize<{ MEMORY_QUEUE_SIZE }>>>,
random: &RefCell<Random>,
mut sim: ExternModuleSimulationState,
) {
let mut random = random.borrow_mut();
let config = memory_interface.config.ty();
let finish_data_ty = memory_interface.finish.data.ty();
let next_op_ids_ty = memory_interface.next_op_ids.ty();
let mut queue: VecDeque<SimValue<MemoryQueueEntry>> = VecDeque::new();
loop {
for entry in &mut queue {
entry.cycles_left = entry.cycles_left.as_int().saturating_sub(1).to_sim_value();
}
let sim_queue = queue_debug
.ty()
.from_iter_sim(MemoryQueueEntry.default_sim(), &queue)
.ok()
.expect("queue is known to be small enough");
sim.write(queue_debug, sim_queue).await;
sim.write_bool(
memory_interface.start.ready,
queue.len() < MEMORY_QUEUE_SIZE && random.next() % 32 != 0,
)
.await;
sim.write(
memory_interface.next_op_ids,
#[hdl(sim)]
next_op_ids_ty.HdlSome(
next_op_ids_ty
.HdlSome
.from_iter_sim(
0u8,
queue
.iter()
.map(|entry| SimValue::into_dyn_int(entry.fetch_block_id.clone())),
)
.ok()
.expect("queue is known to be small enough"),
),
)
.await;
let finish_data = if let Some(entry) = queue
.front()
.filter(|entry| entry.cycles_left.as_int() == 0)
{
#[hdl(sim)]
let MemoryQueueEntry {
addr,
fetch_block_id: _,
cycles_left: _,
} = entry;
let addr = addr.as_int();
let mut read_data =
repeat(0u8, finish_data_ty.HdlSome.read_data.len()).to_sim_value();
let kind = if let Some(data) = read_memory(addr, read_data.len()) {
for (l, r) in read_data.iter_mut().zip(data) {
*l = r.to_sim_value();
}
#[hdl(sim)]
MemoryOperationFinishKind.Success(
#[hdl(sim)]
MemoryOperationKind.Read(),
)
} else {
#[hdl(sim)]
MemoryOperationFinishKind.Error(
#[hdl(sim)]
MemoryOperationErrorKind.Generic(),
)
};
#[hdl(sim)]
finish_data_ty.HdlSome(
#[hdl(sim)]
MemoryOperationFinish::<_> {
kind,
read_data,
config,
},
)
} else {
#[hdl(sim)]
finish_data_ty.HdlNone()
};
sim.write(memory_interface.finish.data, &finish_data).await;
sim.wait_for_clock_edge(cd.clk).await;
println!(
"Dump mock memory queue: {:#?}",
Vec::from_iter(queue.iter().map(|v| {
DebugAsDisplay(format!(
"fid={:#x} addr={:#x}",
v.fetch_block_id.as_int(),
v.addr.as_int(),
))
}))
);
if sim
.read_past_bool(memory_interface.start.ready, cd.clk)
.await
{
#[hdl(sim)]
if let HdlSome(memory_operation_start) =
sim.read_past(memory_interface.start.data, cd.clk).await
{
#[hdl(sim)]
let MemoryOperationStart::<_> {
kind,
addr,
write_data: _,
rw_mask,
op_id,
config: _,
} = memory_operation_start;
#[hdl(sim)]
match kind {
MemoryOperationKind::Read => {}
MemoryOperationKind::Write => unreachable!(),
}
assert!(
rw_mask.iter().all(|v| **v),
"rw_mask should be all true: {rw_mask:?}"
);
let entry = #[hdl(sim)]
MemoryQueueEntry {
addr,
fetch_block_id: SimValue::from_dyn_int(op_id),
cycles_left: MemoryQueueEntry::get_next_delay(&mut random),
};
println!("mock memory start: {entry:#?}");
queue.push_back(entry);
}
}
if sim
.read_past_bool(memory_interface.finish.ready, cd.clk)
.await
{
#[hdl(sim)]
if let HdlSome(finish_data) = finish_data {
let Some(entry) = queue.pop_front() else {
unreachable!();
};
#[hdl(sim)]
let MemoryOperationFinish::<_> {
kind,
read_data,
config: _,
} = finish_data;
let kind = #[hdl(sim)]
match kind {
MemoryOperationFinishKind::Error(_) => Err(()),
MemoryOperationFinishKind::Success(_) => Ok(()),
};
println!(
"mock memory finish: kind={kind:?} read_data={read_data:?} {entry:#?}"
);
}
}
}
}
}
#[hdl_module]
fn dut(config: PhantomConst<CpuConfig>) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let from_next_pc: NextPcToFetchInterface<PhantomConst<CpuConfig>> =
m.input(NextPcToFetchInterface[config]);
#[hdl]
let to_decode: FetchToDecodeInterface<PhantomConst<CpuConfig>> =
m.output(FetchToDecodeInterface[config]);
#[hdl]
let fetch = instance(fetch(config));
#[hdl]
let fetch {
cd: fetch_cd,
memory_interface: fetch_memory_interface,
from_next_pc: fetch_from_next_pc,
to_decode: fetch_to_decode,
} = fetch;
connect(fetch_cd, cd);
connect(fetch_from_next_pc, from_next_pc);
connect(to_decode, fetch_to_decode);
#[hdl]
let mock_memory = instance(mock_memory(config));
#[hdl]
let mock_memory {
cd: mock_memory_cd,
memory_interface: mock_memory_interface,
queue_debug: _,
} = mock_memory;
connect(mock_memory_cd, cd);
connect(mock_memory_interface, fetch_memory_interface);
}
#[derive(Clone)]
struct FetchTestOperation {
start_pc: u64,
fetch_block_id: u8,
fetch_block_data: [u8; FETCH_WIDTH_IN_BYTES],
error: Option<SimValue<MemoryOperationErrorKind>>,
}
impl PartialEq for FetchTestOperation {
#[hdl]
fn eq(&self, other: &Self) -> bool {
let Self {
start_pc,
fetch_block_id,
fetch_block_data,
ref error,
} = *self;
if let Some(error) = error {
#[hdl(sim)]
match error {
MemoryOperationErrorKind::Generic => {}
}
}
start_pc == other.start_pc
&& fetch_block_id == other.fetch_block_id
&& fetch_block_data == other.fetch_block_data
&& error.is_some() == other.error.is_some()
}
}
impl fmt::Debug for FetchTestOperation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
start_pc,
fetch_block_id,
fetch_block_data,
error,
} = self;
let mut debug_struct = f.debug_struct("FetchTestOperation");
debug_struct.field("start_pc", &format_args!("{start_pc:#x}"));
debug_struct.field("fetch_block_id", &format_args!("{fetch_block_id:#x}"));
if fetch_block_data.iter().all(|v| *v == fetch_block_data[0]) {
debug_struct.field(
"fetch_block_data",
&format_args!(
"[b'{}'; {FETCH_WIDTH_IN_BYTES}]",
fetch_block_data[0].escape_ascii(),
),
);
} else {
debug_struct.field(
"fetch_block_data",
&format_args!("b\"{}\"", fetch_block_data.escape_ascii()),
);
}
debug_struct.field("error", error).finish()
}
}
const LOG2_FETCH_WIDTH_IN_BYTES: u8 = 3;
const FETCH_WIDTH_IN_BYTES: usize = 1 << LOG2_FETCH_WIDTH_IN_BYTES;
const LOG2_CACHE_LINE_SIZE_IN_BYTES: u8 = 5;
const CACHE_LINE_SIZE_IN_BYTES: usize = 1 << LOG2_CACHE_LINE_SIZE_IN_BYTES;
// needs to be a multiple of the cache line size
const _: [(); CACHE_LINE_SIZE_IN_BYTES * 2] = [(); MEMORY_DATA.len()];
fn fetch_test_operations() -> Vec<FetchTestOperation> {
#[track_caller]
fn mem_data(r: std::ops::RangeFrom<usize>) -> [u8; FETCH_WIDTH_IN_BYTES] {
*MEMORY_DATA[r]
.as_bytes()
.first_chunk()
.expect("start should be in-range")
}
#[hdl]
fn generic_error() -> SimValue<MemoryOperationErrorKind> {
#[hdl(sim)]
MemoryOperationErrorKind.Generic()
}
let mut last_fetch_block_id = 0u8.wrapping_sub(1);
macro_rules! op {
{
$($field:ident: $value:expr,)*
} => {
FetchTestOperation {
fetch_block_id: {
last_fetch_block_id = last_fetch_block_id.wrapping_add(1);
last_fetch_block_id
},
$($field: $value,)*
}
};
}
vec![
op! {
start_pc: 0x1000,
fetch_block_data: mem_data(0..),
error: None,
},
op! {
start_pc: 0x1010,
fetch_block_data: mem_data(0x10..),
error: None,
},
op! {
start_pc: 0x1020,
fetch_block_data: mem_data(0x20..),
error: None,
},
op! {
start_pc: 0x100,
fetch_block_data: [0; _],
error: Some(generic_error()),
},
op! {
start_pc: MEMORY_ERROR_RANGE.start,
fetch_block_data: [0; _],
error: Some(generic_error()),
},
op! {
start_pc: MEMORY_ERROR_RANGE.start + MEMORY_ERROR_STEP,
fetch_block_data: [0; _],
error: Some(generic_error()),
},
op! {
start_pc: MEMORY_ERROR_RANGE.start + MEMORY_ERROR_STEP * 2,
fetch_block_data: [0; _],
error: Some(generic_error()),
},
op! {
start_pc: MEMORY_ERROR_RANGE.start + MEMORY_ERROR_STEP * 3,
fetch_block_data: [0; _],
error: Some(generic_error()),
},
op! {
start_pc: 0x1000,
fetch_block_data: mem_data(0..),
error: None,
},
op! {
start_pc: 0x1010,
fetch_block_data: mem_data(0x10..),
error: None,
},
op! {
start_pc: 0x1020,
fetch_block_data: mem_data(0x20..),
error: None,
},
op! {
start_pc: 0x1000,
fetch_block_data: mem_data(0..),
error: None,
},
op! {
start_pc: 0x1010,
fetch_block_data: mem_data(0x10..),
error: None,
},
op! {
start_pc: 0x1020,
fetch_block_data: mem_data(0x20..),
error: None,
},
op! {
start_pc: MEMORY_RANGE2.start,
fetch_block_data: [0xF2; _],
error: None,
},
op! {
start_pc: MEMORY_RANGE2.start,
fetch_block_data: [0xF2; _],
error: None,
},
op! {
start_pc: MEMORY_RANGE2.start,
fetch_block_data: [0xF2; _],
error: None,
},
op! {
start_pc: MEMORY_RANGE2.start,
fetch_block_data: [0xF2; _],
error: None,
},
op! {
start_pc: MEMORY_RANGE2.start,
fetch_block_data: [0xF2; _],
error: None,
},
op! {
start_pc: MEMORY_RANGE2.start,
fetch_block_data: [0xF2; _],
error: None,
},
op! {
start_pc: MEMORY_RANGE2.start,
fetch_block_data: [0xF2; _],
error: None,
},
op! {
start_pc: 0x1000,
fetch_block_data: mem_data(0..),
error: None,
},
op! {
start_pc: 0x1000,
fetch_block_data: mem_data(0..),
error: None,
},
op! {
start_pc: 0x1000,
fetch_block_data: mem_data(0..),
error: None,
},
op! {
start_pc: 0x1000,
fetch_block_data: mem_data(0..),
error: None,
},
op! {
start_pc: 0x1000,
fetch_block_data: mem_data(0..),
error: None,
},
op! {
start_pc: 0x1000,
fetch_block_data: mem_data(0..),
error: None,
},
]
}
#[test]
#[hdl]
fn test_fetch() {
let _n = SourceLocation::normalize_files_for_tests();
let mut config = CpuConfig::new(
vec![
UnitConfig::new(UnitKind::AluBranch),
UnitConfig::new(UnitKind::AluBranch),
],
NonZeroUsize::new(20).unwrap(),
);
config.fetch_width = NonZeroUsize::new(2).unwrap();
config.log2_fetch_width_in_bytes = LOG2_FETCH_WIDTH_IN_BYTES;
config.log2_cache_line_size_in_bytes = LOG2_CACHE_LINE_SIZE_IN_BYTES;
config.log2_l1_i_cache_line_count = 4;
let m = dut(PhantomConst::new_sized(config));
let mut sim = Simulation::new(m);
let writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
struct DumpVcdOnDrop {
writer: Option<RcWriter>,
}
impl Drop for DumpVcdOnDrop {
fn drop(&mut self) {
if let Some(mut writer) = self.writer.take() {
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
}
}
}
let mut writer = DumpVcdOnDrop {
writer: Some(writer),
};
let from_next_pc_ty = sim.io().from_next_pc.ty();
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, true);
sim.write(
sim.io().from_next_pc.cancel.data,
from_next_pc_ty.cancel.data.HdlNone(),
);
sim.write(
sim.io().from_next_pc.fetch.data,
from_next_pc_ty.fetch.data.HdlNone(),
);
sim.write(sim.io().to_decode.fetched.ready, true);
sim.write(
sim.io().to_decode.next_fetch_block_ids,
HdlSome(
sim.io()
.ty()
.to_decode
.next_fetch_block_ids
.HdlSome
.new_sim(0u8),
),
);
let operations = fetch_test_operations();
let mut started_operations = 0;
let mut finished_operations = 0;
for cycle in 0..150 {
sim.write(
sim.io().from_next_pc.fetch.data,
if let Some(op) = operations.get(started_operations) {
#[hdl(sim)]
HdlSome(
#[hdl(sim)]
NextPcToFetchInterfaceInner {
start_pc: op.start_pc,
fetch_block_id: op.fetch_block_id,
},
)
} else {
#[hdl(sim)]
HdlNone()
},
);
sim.advance_time(SimDuration::from_nanos(500));
#[hdl(sim)]
if let HdlSome(next_fetch_block_ids) = sim.read(sim.io().from_next_pc.next_fetch_block_ids)
{
let next_fetch_block_ids = ArrayVec::elements_sim_ref(&next_fetch_block_ids);
let expected_next_fetch_block_ids = Vec::from_iter(
operations
.get(finished_operations..started_operations)
.unwrap_or(&[])
.iter()
.map(|op| op.fetch_block_id.to_sim_value()),
);
println!("expected_next_fetch_block_ids={expected_next_fetch_block_ids:?}");
assert_eq!(next_fetch_block_ids, expected_next_fetch_block_ids);
}
if sim.read_bool(sim.io().from_next_pc.fetch.ready) {
#[hdl(sim)]
if let HdlSome(_) = sim.read(sim.io().from_next_pc.fetch.data) {
println!("started fetch: {:#?}", operations[started_operations]);
started_operations += 1;
}
} else {
println!("not ready to start fetch");
}
if sim.read_bool(sim.io().to_decode.fetched.ready) {
#[hdl(sim)]
if let HdlSome(fetched) = sim.read(sim.io().to_decode.fetched.data) {
#[hdl(sim)]
let FetchToDecodeInterfaceInner::<_> {
start_pc,
fetch_block_id,
fetch_block_data,
error,
config: _,
} = &fetched;
let Some(expected_op) = operations.get(finished_operations) else {
panic!("too many finished operations: {fetched:#?}");
};
let op = FetchTestOperation {
start_pc: start_pc.as_int(),
fetch_block_id: fetch_block_id.as_int(),
error: #[hdl(sim)]
match error {
HdlSome(e) => Some(e.clone()),
HdlNone => None,
},
fetch_block_data: std::array::from_fn(|i| fetch_block_data[i].as_int()),
};
println!("finished fetch: op={op:#?}");
assert_eq!(
op, *expected_op,
"cycle={cycle} finished_operations={finished_operations}",
);
finished_operations += 1;
} else {
println!("not ready to finish fetch");
}
}
println!("clock tick: {cycle}");
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(SimDuration::from_nanos(500));
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, false);
}
assert_eq!(finished_operations, operations.len());
let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("expected/fetch.vcd") {
panic!();
}
}

View file

@ -1,305 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use cpu::{
main_memory_and_io::{
AddressRange, MemoryInterface, MemoryInterfaceConfig, MemoryOperationErrorKind,
MemoryOperationFinish, MemoryOperationFinishKind, MemoryOperationKind,
MemoryOperationStart, main_memory_and_io, simple_uart::SIMPLE_UART_TRANSMIT_OFFSET,
},
next_pc::FETCH_BLOCK_ID_WIDTH,
};
use fayalite::{prelude::*, sim::vcd::VcdWriterDecls, util::RcWriter};
use std::num::{NonZeroU64, NonZeroUsize, Wrapping};
const CLOCK_FREQUENCY: f64 = 2_000_000.0;
const fn half_period(frequency: f64) -> SimDuration {
SimDuration::from_attos((SimDuration::from_secs(1).as_attos() as f64 / frequency / 2.0) as u128)
}
const CLOCK_HALF_PERIOD: SimDuration = half_period(CLOCK_FREQUENCY);
fn clock_input_properties() -> PhantomConst<peripherals::ClockInputProperties> {
peripherals::ClockInput::new(CLOCK_FREQUENCY).properties
}
const SRAM_START: u64 = 0x1000;
const SRAM_SIZE: NonZeroU64 = NonZeroU64::new(0x30).unwrap();
const UART_START: u64 = 0x100000;
const UART_BAUD_RATE: f64 = 115200.0;
const UART_RECEIVER_QUEUE_SIZE: NonZeroUsize = NonZeroUsize::new(16).unwrap();
const SRAM_INITIAL_CONTENTS: &[u8; SRAM_SIZE.get() as usize] =
b"This is a main memory and I/O test. ";
const SRAM_WRITING_CONTENTS: &[u8; SRAM_SIZE.get() as usize] =
b"Testing. Hello World. abcdefghijklmnopqrstuvwxyz";
const CONFIG: MemoryInterfaceConfig =
MemoryInterfaceConfig::new(3, 8, FETCH_BLOCK_ID_WIDTH, AddressRange::Full);
const BUS_WIDTH_IN_BYTES: usize = CONFIG.bus_width_in_bytes();
fn config() -> PhantomConst<MemoryInterfaceConfig> {
PhantomConst::new_sized(CONFIG)
}
#[hdl_module]
fn test_harness() {
let config = config();
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let memory_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
m.input(MemoryInterface[config]);
#[hdl]
let dut = instance(main_memory_and_io(
config,
clock_input_properties(),
AddressRange::Limited {
start: Wrapping(SRAM_START),
size: SRAM_SIZE,
},
SRAM_INITIAL_CONTENTS,
UART_START,
UART_BAUD_RATE,
UART_RECEIVER_QUEUE_SIZE,
));
connect(dut.cd, cd);
connect(dut.memory_interface, memory_interface);
connect(dut.uart.rx, dut.uart.tx); // loop back for testing
}
struct MainMemoryAndIoTester {
sim: Simulation<test_harness>,
next_op_id: u64,
}
impl MainMemoryAndIoTester {
fn reset(&mut self) {
let sim = &mut self.sim;
sim.write(
sim.io().memory_interface.start.data,
sim.io().ty().memory_interface.start.data.HdlNone(),
);
sim.write(sim.io().memory_interface.finish.ready, false);
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, false);
}
fn wait(&mut self, cycles: u64) {
let sim = &mut self.sim;
sim.write(
sim.io().memory_interface.start.data,
sim.io().ty().memory_interface.start.data.HdlNone(),
);
sim.write(sim.io().memory_interface.finish.ready, false);
for _ in 0..cycles {
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, false);
}
}
#[track_caller]
#[hdl]
fn try_rw_op(
&mut self,
addr: u64,
write: Option<[u8; BUS_WIDTH_IN_BYTES]>,
rw_mask: [bool; BUS_WIDTH_IN_BYTES],
max_wait: u64,
) -> Result<[u8; BUS_WIDTH_IN_BYTES], SimValue<MemoryOperationErrorKind>> {
let config = config();
let sim = &mut self.sim;
let op_id = self.next_op_id.cast_to(UInt[FETCH_BLOCK_ID_WIDTH]);
self.next_op_id += 1;
sim.write(
sim.io().memory_interface.start.data,
#[hdl(sim)]
(sim.io().ty().memory_interface.start.data).HdlSome(match write {
Some(write) =>
{
#[hdl(sim)]
MemoryOperationStart::<_> {
kind: #[hdl(sim)]
MemoryOperationKind.Write(),
addr,
write_data: &write[..],
rw_mask: &rw_mask[..],
op_id,
config,
}
}
None =>
{
#[hdl(sim)]
MemoryOperationStart::<_> {
kind: #[hdl(sim)]
MemoryOperationKind.Read(),
addr,
write_data: &[0u8; BUS_WIDTH_IN_BYTES][..],
rw_mask: &rw_mask[..],
op_id,
config,
}
}
}),
);
sim.write(sim.io().memory_interface.finish.ready, true);
let mut cleared_start = false;
for _ in 0..max_wait {
sim.advance_time(CLOCK_HALF_PERIOD);
let start_ready = sim.read_bool(sim.io().memory_interface.start.ready);
let finish_data = sim.read(sim.io().memory_interface.finish.data);
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, false);
if start_ready && !cleared_start {
cleared_start = true;
sim.write(
sim.io().memory_interface.start.data,
sim.io().ty().memory_interface.start.data.HdlNone(),
);
}
#[hdl(sim)]
if let HdlSome(finish) = finish_data {
#[hdl(sim)]
let MemoryOperationFinish::<_> {
kind,
read_data,
config: _,
} = finish;
#[hdl(sim)]
match kind {
MemoryOperationFinishKind::Success(_) => {
return Ok(std::array::from_fn(|i| read_data[i].as_int()));
}
MemoryOperationFinishKind::Error(e) => {
return Err(e);
}
};
}
}
panic!("waited too many cycles");
}
#[track_caller]
fn try_read<const N: usize>(
&mut self,
addr: u64,
max_wait: u64,
) -> Result<[u8; N], SimValue<MemoryOperationErrorKind>> {
const { assert!(N <= BUS_WIDTH_IN_BYTES) }
let bus_addr = addr - addr % BUS_WIDTH_IN_BYTES as u64;
let mut rw_mask = [false; BUS_WIDTH_IN_BYTES];
let start = (addr % BUS_WIDTH_IN_BYTES as u64) as usize;
rw_mask[start..start + N].fill(true);
self.try_rw_op(bus_addr, None, rw_mask, max_wait)
.map(|v| std::array::from_fn(|i| v[i + start]))
}
#[track_caller]
fn read<const N: usize>(&mut self, addr: u64, max_wait: u64) -> [u8; N] {
self.try_read(addr, max_wait).expect("read returned error")
}
#[track_caller]
fn try_write<const N: usize>(
&mut self,
addr: u64,
data: [u8; N],
max_wait: u64,
) -> Result<(), SimValue<MemoryOperationErrorKind>> {
const { assert!(N <= BUS_WIDTH_IN_BYTES) }
let bus_addr = addr - addr % BUS_WIDTH_IN_BYTES as u64;
let mut rw_mask = [false; BUS_WIDTH_IN_BYTES];
let mut write_data = [0u8; BUS_WIDTH_IN_BYTES];
let start = (addr % BUS_WIDTH_IN_BYTES as u64) as usize;
rw_mask[start..start + N].fill(true);
write_data[start..start + N].copy_from_slice(&data);
self.try_rw_op(bus_addr, Some(write_data), rw_mask, max_wait)?;
Ok(())
}
#[track_caller]
fn write<const N: usize>(&mut self, addr: u64, data: [u8; N], max_wait: u64) {
self.try_write(addr, data, max_wait)
.expect("read returned error")
}
}
#[test]
#[hdl]
fn test_main_memory_and_io() {
let _n = SourceLocation::normalize_files_for_tests();
let mut sim = Simulation::new(test_harness());
let writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
struct DumpVcdOnDrop {
writer: Option<RcWriter>,
}
impl Drop for DumpVcdOnDrop {
fn drop(&mut self) {
if let Some(mut writer) = self.writer.take() {
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
}
}
}
let mut writer = DumpVcdOnDrop {
writer: Some(writer),
};
let mut tester = MainMemoryAndIoTester { sim, next_op_id: 0 };
tester.reset();
const UART_MESSAGE: &str = "Test.";
for b in UART_MESSAGE.bytes() {
tester.write(UART_START + SIMPLE_UART_TRANSMIT_OFFSET, [b], 200);
}
for (i, expected_chunk) in SRAM_INITIAL_CONTENTS
.as_chunks::<BUS_WIDTH_IN_BYTES>()
.0
.iter()
.enumerate()
{
let addr = (i as u64 * BUS_WIDTH_IN_BYTES as u64) + SRAM_START;
let chunk = tester.read::<BUS_WIDTH_IN_BYTES>(addr, 10);
assert_eq!(chunk, *expected_chunk);
}
for (i, chunk) in SRAM_WRITING_CONTENTS
.as_chunks::<BUS_WIDTH_IN_BYTES>()
.0
.iter()
.enumerate()
{
let addr = (i as u64 * BUS_WIDTH_IN_BYTES as u64) + SRAM_START;
tester.write(addr, *chunk, 10);
}
for (i, expected_chunk) in SRAM_WRITING_CONTENTS
.as_chunks::<BUS_WIDTH_IN_BYTES>()
.0
.iter()
.enumerate()
{
let addr = (i as u64 * BUS_WIDTH_IN_BYTES as u64) + SRAM_START;
let chunk = tester.read::<BUS_WIDTH_IN_BYTES>(addr, 10);
assert_eq!(chunk, *expected_chunk);
}
tester.wait(400);
for expected_byte in UART_MESSAGE.bytes() {
let [byte] = tester.read(UART_START + SIMPLE_UART_TRANSMIT_OFFSET, 10);
assert_eq!(byte, expected_byte);
}
let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("expected/main_memory_and_io.vcd") {
panic!();
}
}

View file

@ -1,750 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use cpu::{
main_memory_and_io::{
AddressRange, MemoryInterface, MemoryInterfaceConfig, MemoryOperationErrorKind,
MemoryOperationFinish, MemoryOperationFinishKind, MemoryOperationKind,
MemoryOperationStart, memory_interface_adapter_no_split,
},
next_pc::FETCH_BLOCK_ID_WIDTH,
util::array_vec::ArrayVec,
};
use fayalite::{
bundle::{BundleField, BundleType},
intern::{Intern, Interned},
module::instance_with_loc,
prelude::*,
sim::vcd::VcdWriterDecls,
util::RcWriter,
};
use std::{cell::Cell, collections::VecDeque, fmt};
fn get_next_delay(delay_sequence_index: &Cell<u64>) -> u8 {
let index = delay_sequence_index.get();
delay_sequence_index.set(delay_sequence_index.get().wrapping_add(1));
// make a pseudo-random number deterministically based on index
let random = index
.wrapping_add(1)
.wrapping_mul(0x8c16a62518f86883) // random prime
.rotate_left(32)
.wrapping_mul(0xf807b7df2082353d) // random prime
.rotate_right(60);
const DELAYS: &[u8; 0x20] = &[
0, 0, 0, 0, 0, 0, 0, 0, //
1, 1, 1, 1, 1, 1, 1, 1, //
2, 2, 2, 2, 2, 2, 2, 2, //
3, 3, 3, 3, 4, 5, 6, 20, //
];
DELAYS[(random & 0x1F) as usize]
}
#[hdl_module(extern)]
fn mock_memory(memory: Memory) {
let Memory { config, contents } = memory;
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let input_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
m.input(MemoryInterface[config]);
m.register_clock_for_past(cd.clk);
#[hdl]
async fn run(
cd: Expr<ClockDomain>,
input_interface: Expr<MemoryInterface<PhantomConst<MemoryInterfaceConfig>>>,
config: PhantomConst<MemoryInterfaceConfig>,
contents: Interned<str>,
delay_sequence_index: &Cell<u64>,
mut sim: ExternModuleSimulationState,
) {
#[derive(Debug)]
struct Op {
cycles_left: u8,
op_id: SimValue<UInt>,
finish: SimValue<MemoryOperationFinish<PhantomConst<MemoryInterfaceConfig>>>,
}
let mut ops = VecDeque::<Op>::with_capacity(config.get().queue_capacity.get());
let finish_ty = input_interface.ty().finish.data.HdlSome;
loop {
for op in &mut ops {
op.cycles_left = op.cycles_left.saturating_sub(1);
}
let next_op_ids_ty = input_interface.ty().next_op_ids.HdlSome;
sim.write(
input_interface.next_op_ids,
#[hdl(sim)]
(input_interface.ty().next_op_ids).HdlSome(
next_op_ids_ty
.from_iter_sim(
next_op_ids_ty.element().zero(),
ops.iter().map(|op| &op.op_id),
)
.expect("known to fit"),
),
)
.await;
if let Some(Op {
cycles_left: 0,
op_id: _,
finish,
}) = ops.front()
{
sim.write(
input_interface.finish.data,
#[hdl(sim)]
(input_interface.ty().finish.data).HdlSome(finish),
)
.await;
} else {
sim.write(
input_interface.finish.data,
#[hdl(sim)]
(input_interface.ty().finish.data).HdlNone(),
)
.await;
}
sim.write_bool(
input_interface.start.ready,
ops.len() < config.get().queue_capacity.get(),
)
.await;
sim.wait_for_clock_edge(cd.clk).await;
if sim
.read_past_bool(input_interface.finish.ready, cd.clk)
.await
{
dbg!(ops.pop_front_if(|op| op.cycles_left == 0));
}
if sim
.read_past_bool(input_interface.start.ready, cd.clk)
.await
{
#[hdl(sim)]
if let HdlSome(start) = sim.read_past(input_interface.start.data, cd.clk).await {
#[hdl(sim)]
let MemoryOperationStart::<_> {
kind,
addr,
write_data,
rw_mask,
op_id,
config: _,
} = start;
let mut error = false;
let mut read_data = vec![0u8; finish_ty.read_data.len()];
#[hdl(sim)]
match &kind {
MemoryOperationKind::Read => {
for (i, v) in read_data.iter_mut().enumerate() {
if *rw_mask[i] {
let addr = addr.as_int().wrapping_add(i as u64);
let offset =
addr.wrapping_sub(config.get().address_range.start().0);
if !config.get().address_range.contains(addr)
|| offset >= contents.len() as u64
{
error = true;
break;
}
*v = contents.as_bytes()[offset as usize];
println!(
"reading byte at {addr:#x} (offset={offset:#x}) -> {v:#x} (contents={contents:?})",
);
}
}
if !error {
println!(
"read chunk at {addr:#x}: {:#x?}",
std::fmt::from_fn(|f| f
.debug_map()
.entries(
read_data
.iter()
.enumerate()
.filter_map(|(i, &v)| rw_mask[i].then(|| (i, v)))
)
.finish()),
addr = addr.as_int(),
);
}
}
MemoryOperationKind::Write => {
todo!("write {write_data:?}");
}
}
let finish_kind = if error {
println!("error at: {addr} config: {config:?}");
read_data.fill(0);
#[hdl(sim)]
MemoryOperationFinishKind.Error(
#[hdl(sim)]
MemoryOperationErrorKind.Generic(),
)
} else {
#[hdl(sim)]
MemoryOperationFinishKind.Success(kind)
};
ops.push_back(Op {
cycles_left: get_next_delay(delay_sequence_index),
op_id,
finish: #[hdl(sim)]
MemoryOperationFinish::<_> {
kind: finish_kind,
read_data,
config,
},
});
}
}
}
}
m.extern_module_simulation_fn(
(cd, input_interface, config, contents),
async |(cd, input_interface, config, contents), mut sim| {
// intentionally have a different sequence each time we're reset
let delay_sequence_index = Cell::new(0);
sim.resettable(
cd,
async |mut sim| {
sim.write(
input_interface.next_op_ids,
#[hdl(sim)]
(input_interface.ty().next_op_ids).HdlNone(),
)
.await;
sim.write_bool(input_interface.start.ready, false).await;
sim.write(
input_interface.finish.data,
#[hdl(sim)]
(input_interface.ty().finish.data).HdlNone(),
)
.await;
},
|sim, ()| {
run(
cd,
input_interface,
config,
contents,
&delay_sequence_index,
sim,
)
},
)
.await
},
);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct Memory {
contents: Interned<str>,
config: PhantomConst<MemoryInterfaceConfig>,
}
impl Memory {
fn new(
contents: impl AsRef<str>,
log2_bus_width_in_bytes: u8,
address_range: AddressRange,
) -> Self {
Self {
contents: contents.as_ref().intern(),
config: PhantomConst::new_sized(MemoryInterfaceConfig::new(
log2_bus_width_in_bytes,
8,
FETCH_BLOCK_ID_WIDTH,
address_range,
)),
}
}
}
#[hdl_module(extern)]
fn mock_cpu(memories: Interned<[Memory]>) {
const LOG2_BUS_WIDTH: u8 = 3;
const BUS_WIDTH: usize = 1 << LOG2_BUS_WIDTH;
let config = PhantomConst::new_sized(MemoryInterfaceConfig::new(
LOG2_BUS_WIDTH,
8,
FETCH_BLOCK_ID_WIDTH,
AddressRange::Full,
));
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let output_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
m.output(MemoryInterface[config]);
#[hdl]
let finished: Bool = m.output();
m.register_clock_for_past(cd.clk);
#[derive(PartialEq, Clone, Copy)]
struct Op {
addr: u64,
read_mask: [bool; BUS_WIDTH],
}
impl fmt::Debug for Op {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { addr, read_mask } = self;
f.debug_struct("Op")
.field("addr", &fmt::from_fn(|f| write!(f, "{addr:#x}")))
.field("read_mask", read_mask)
.finish()
}
}
#[hdl]
async fn generator(
cd: Expr<ClockDomain>,
output_interface: Expr<MemoryInterface<PhantomConst<MemoryInterfaceConfig>>>,
config: PhantomConst<MemoryInterfaceConfig>,
sequence: &[Op],
delay_sequence_index: &Cell<u64>,
mut sim: ExternModuleSimulationState,
) {
println!("generator: start");
let start_ty = MemoryOperationStart[config];
for (op_index, op) in sequence.iter().enumerate() {
sim.write(
output_interface.start.data,
#[hdl(sim)]
(output_interface.ty().start.data).HdlNone(),
)
.await;
let delay = get_next_delay(delay_sequence_index);
println!("generator: delay by {delay}");
for i in 0..delay {
println!("generator: delay cycle {i}");
sim.wait_for_clock_edge(cd.clk).await;
}
println!("generator: generated {op:?}");
sim.write(
output_interface.start.data,
#[hdl(sim)]
(output_interface.ty().start.data).HdlSome(
#[hdl(sim)]
MemoryOperationStart::<_> {
kind: #[hdl(sim)]
MemoryOperationKind.Read(),
addr: op.addr,
write_data: &[0u8; BUS_WIDTH][..],
rw_mask: &op.read_mask[..],
op_id: op_index.cast_to(start_ty.op_id),
config,
},
),
)
.await;
sim.wait_for_clock_edge(cd.clk).await;
while !sim
.read_past_bool(output_interface.start.ready, cd.clk)
.await
{
println!("generator: start.ready was false");
sim.wait_for_clock_edge(cd.clk).await;
}
}
sim.write(
output_interface.start.data,
#[hdl(sim)]
(output_interface.ty().start.data).HdlNone(),
)
.await;
}
#[hdl]
async fn checker(
cd: Expr<ClockDomain>,
output_interface: Expr<MemoryInterface<PhantomConst<MemoryInterfaceConfig>>>,
config: PhantomConst<MemoryInterfaceConfig>,
sequence: &[Op],
memories: Interned<[Memory]>,
delay_sequence_index: &Cell<u64>,
sim: &mut ExternModuleSimulationState,
) {
println!("checker: start");
for op in sequence {
sim.write_bool(output_interface.finish.ready, false).await;
let delay = get_next_delay(delay_sequence_index);
println!("checker: delay by {delay}");
for i in 0..delay {
println!("checker: delay cycle {i}");
sim.wait_for_clock_edge(cd.clk).await;
}
sim.write_bool(output_interface.finish.ready, true).await;
sim.wait_for_clock_edge(cd.clk).await;
let mut finish = loop {
#[hdl(sim)]
if let HdlSome(finish) = sim.read_past(output_interface.finish.data, cd.clk).await {
break finish;
}
println!("checker: finish.data was HdlNone");
sim.wait_for_clock_edge(cd.clk).await;
};
println!("checker: checking for {op:?}");
let finish_unmasked = finish.clone();
for (v, &mask) in finish.read_data.iter_mut().zip(&op.read_mask) {
if !mask {
*v = 0u8.to_sim_value(); // ignore outputs for ignored bytes
}
}
let mut expected_finish = memories
.iter()
.find(|m| dbg!(dbg!(m.config.get().address_range).contains(op.addr)))
.and_then(
|&Memory {
config: memory_config,
contents,
}|
-> Option<_> {
let mut read_data = [0u8; BUS_WIDTH];
let mut first_enabled = None;
let mut last_enabled = None;
for (i, &mask) in op.read_mask.iter().enumerate() {
if mask {
first_enabled.get_or_insert(i);
last_enabled = Some(i);
read_data[i] = *dbg!(
dbg!(contents).as_bytes().get(
dbg!(usize::try_from(
op.addr.wrapping_add(i as u64).wrapping_sub(
memory_config.get().address_range.start().0,
),
))
.ok()?,
)
)?;
}
}
if let (Some(first_enabled), Some(last_enabled)) =
(first_enabled, last_enabled)
{
let log2_bus_width_in_bytes =
memory_config.get().log2_bus_width_in_bytes;
if first_enabled >> log2_bus_width_in_bytes
!= last_enabled >> log2_bus_width_in_bytes
{
// this operation requires more than one operation at the final memory,
// so it gets turned into an error since we're using
// memory_interface_adapter_no_split
return None;
}
}
Some(
#[hdl(sim)]
MemoryOperationFinish::<_> {
kind: #[hdl(sim)]
MemoryOperationFinishKind.Success(
#[hdl(sim)]
MemoryOperationKind.Read(),
),
read_data: &read_data[..],
config,
},
)
},
)
.unwrap_or_else(|| {
#[hdl(sim)]
MemoryOperationFinish::<_> {
kind: #[hdl(sim)]
MemoryOperationFinishKind.Error(
#[hdl(sim)]
MemoryOperationErrorKind.Generic(),
),
read_data: &[0u8; BUS_WIDTH][..],
config,
}
});
// make SimValue fill in the enum padding so they format the same
SimValue::bits_mut(&mut finish);
SimValue::bits_mut(&mut expected_finish);
assert!(
format!("{finish:#?}") == format!("{expected_finish:#?}"),
"op={op:#?}\nexpected_finish={expected_finish:#?}\n\
finish={finish:#?}\nfinish_unmasked={finish_unmasked:#?}"
);
}
}
m.extern_module_simulation_fn(
(cd, output_interface, finished, config, memories),
async |(cd, output_interface, finished, config, memories), mut sim| {
sim.write_bool(finished, false).await;
sim.write_bool(output_interface.finish.ready, false).await;
sim.write(
output_interface.start.data,
#[hdl(sim)]
(output_interface.ty().start.data).HdlNone(),
)
.await;
// intentionally have a different sequence each time we're reset
let generator_delay_sequence_index = Cell::new(1 << 63);
let checker_delay_sequence_index = Cell::new(1 << 62);
let mut sequence = Vec::new();
sequence.push(Op {
addr: !0 << 16,
read_mask: [true; _],
});
for (i, memory) in memories.iter().enumerate() {
assert!(
memory
.config
.get()
.address_range
.start()
.0
.is_multiple_of(BUS_WIDTH as u64)
);
assert!(
memory
.config
.get()
.address_range
.last()
.wrapping_add(1)
.is_multiple_of(BUS_WIDTH as u64)
);
if i == 0 {
for log2_read_size in 0..=LOG2_BUS_WIDTH {
let read_size = 1 << log2_read_size;
for offset in (0..BUS_WIDTH).step_by(read_size) {
sequence.push(Op {
addr: memory.config.get().address_range.start().0,
read_mask: std::array::from_fn(|byte_index| {
byte_index
.checked_sub(offset)
.is_some_and(|v| v < read_size)
}),
});
}
}
}
for (addr, chunk) in (memory.config.get().address_range.start().0..)
.step_by(BUS_WIDTH)
.zip(memory.contents.as_bytes().chunks(BUS_WIDTH))
{
let size = memory.config.get().bus_width_in_bytes();
for offset in (0..BUS_WIDTH).step_by(size) {
let mut op = Op {
addr,
read_mask: std::array::from_fn(|i| i >= offset && i < offset + size),
};
op.read_mask[chunk.len()..].fill(false);
if op.read_mask.contains(&true) && sequence.last() != Some(&op) {
sequence.push(op);
}
}
}
}
sequence.extend_from_within(..);
sequence.extend_from_within(..);
sim.fork_join_scope(async |scope, mut sim| {
scope.spawn_detached(async |_scope, mut sim| {
sim.resettable(
cd,
async |mut sim| {
sim.write(
output_interface.start.data,
#[hdl(sim)]
(output_interface.ty().start.data).HdlNone(),
)
.await;
},
|sim, ()| {
generator(
cd,
output_interface,
config,
&sequence,
&generator_delay_sequence_index,
sim,
)
},
)
.await
});
scope.spawn_detached(async |_scope, mut sim| {
sim.resettable(
cd,
async |_| {},
async |mut sim, ()| {
let mut expected_range = 0u32..0u32;
loop {
sim.wait_for_clock_edge(cd.clk).await;
let next_op_ids =
sim.read_past(output_interface.next_op_ids, cd.clk).await;
#[hdl(sim)]
if let HdlSome(next_op_ids) = next_op_ids {
let op_id_ty = next_op_ids.ty().element();
let capacity = next_op_ids.ty().capacity();
let next_op_ids = ArrayVec::elements_sim_ref(&next_op_ids);
let expected_next_op_ids = Vec::from_iter(
expected_range
.clone()
.map(|i| i.cast_to(op_id_ty).into_sim_value()),
);
assert_eq!(
next_op_ids,
expected_next_op_ids,
"{:#x}..{:#x} ({expected_range:?}){}",
expected_range.start,
expected_range.end,
if expected_range.len() > capacity {
"\nexpected_range is bigger than capacity"
} else {
""
}
);
}
#[hdl(sim)]
if let HdlSome(_) =
sim.read_past(output_interface.start.data, cd.clk).await
{
if sim
.read_past_bool(output_interface.start.ready, cd.clk)
.await
{
expected_range.end += 1;
}
}
#[hdl(sim)]
if let HdlSome(_) =
sim.read_past(output_interface.finish.data, cd.clk).await
{
if sim
.read_past_bool(output_interface.finish.ready, cd.clk)
.await
{
expected_range.start += 1;
}
}
}
},
)
.await;
});
sim.resettable(
cd,
async |mut sim| {
sim.write_bool(finished, false).await;
sim.write_bool(output_interface.finish.ready, false).await;
},
async |mut sim, ()| {
checker(
cd,
output_interface,
config,
&sequence,
memories,
&checker_delay_sequence_index,
&mut sim,
)
.await;
sim.write_bool(finished, true).await;
loop {
sim.write_bool(output_interface.finish.ready, true).await;
sim.wait_for_clock_edge(cd.clk).await;
#[hdl(sim)]
if let HdlSome(finish) =
sim.read_past(output_interface.finish.data, cd.clk).await
{
panic!("spurious finished transaction: {finish:#?}");
}
}
},
)
.await
})
.await;
},
);
}
#[hdl_module]
fn memory_interface_adapter_no_split_dut(memories: Interned<[Memory]>) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let finished: Bool = m.output();
#[hdl]
let mock_cpu = instance(mock_cpu(memories));
connect(mock_cpu.cd, cd);
connect(finished, mock_cpu.finished);
let (fields, inputs): (Vec<_>, Vec<_>) = memories
.iter()
.enumerate()
.map(|(index, &memory)| {
let mock_mem = instance_with_loc(
&format!("mock_mem_{index}"),
mock_memory(memory),
SourceLocation::caller(),
);
connect(mock_mem.cd, cd);
(
BundleField {
name: format!("{index}").intern_deref(),
flipped: false,
ty: MemoryInterface[memory.config].canonical(),
},
mock_mem.input_interface,
)
})
.unzip();
let bundle_ty = Bundle::new(fields.intern_deref());
#[hdl]
let adapter = instance(memory_interface_adapter_no_split(
mock_cpu.ty().output_interface.config,
bundle_ty,
));
connect(adapter.cd, cd);
connect(adapter.input_interface, mock_cpu.output_interface);
for (field, input) in bundle_ty.fields().into_iter().zip(inputs) {
connect(input, Expr::field(adapter.output_interfaces, &field.name));
}
}
#[test]
#[hdl]
fn test_memory_interface_adapter_no_split() {
let _n = SourceLocation::normalize_files_for_tests();
let memories = vec![
Memory::new("Testing", 3, AddressRange::from_range(0x1000..0x2000)),
Memory::new("Memory2.", 2, AddressRange::from_range(0x2000..0x2010)),
Memory::new("Contents Test", 0, AddressRange::from_range(0x3000..0x3100)),
]
.intern_deref();
let m = memory_interface_adapter_no_split_dut(memories);
let mut sim = Simulation::new(m);
let writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
struct DumpVcdOnDrop {
writer: Option<RcWriter>,
}
impl Drop for DumpVcdOnDrop {
fn drop(&mut self) {
if let Some(mut writer) = self.writer.take() {
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
}
}
}
let mut writer = DumpVcdOnDrop {
writer: Some(writer),
};
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, true);
for cycle in 0..500 {
println!("cycle: {cycle}");
sim.advance_time(SimDuration::from_nanos(500));
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(SimDuration::from_nanos(500));
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, false);
}
sim.advance_time(SimDuration::from_nanos(500));
assert!(sim.read_bool(sim.io().finished));
let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("expected/memory_interface_adapter_no_split.vcd") {
panic!();
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,17 +1,20 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
#![cfg(todo)]
use cpu::{
config::{CpuConfig, UnitConfig},
instruction::{AddSubMOp, LogicalMOp, Lut4, MOp, MOpDestReg, MOpRegNum, OutputIntegerMode},
reg_alloc::{FetchedDecodedMOp, reg_alloc},
instruction::{AddSubMOp, LogicalMOp, MOp, MOpDestReg, MOpRegNum, OutputIntegerMode},
reg_alloc::{reg_alloc, FetchedDecodedMOp},
register::{FlagsMode, PRegFlagsPowerISA},
unit::{GlobalState, UnitKind},
};
use fayalite::{
assert_export_firrtl,
firrtl::ExportOptions,
prelude::*,
sim::{Simulation, time::SimDuration, vcd::VcdWriterDecls},
sim::{time::SimDuration, vcd::VcdWriterDecls, Simulation},
util::RcWriter,
};
use std::num::NonZeroUsize;
@ -60,7 +63,7 @@ fn test_reg_alloc() {
[HdlSome(()), HdlNone()],
},
[0u8; 2],
0x12345678u32.cast_to_static::<SInt<_>>(),
0x12345678u32.cast_to_static(),
OutputIntegerMode.DupLow32(),
false,
false,
@ -82,7 +85,7 @@ fn test_reg_alloc() {
[HdlSome(()), HdlNone()],
},
[1u8, 0, 0],
1.cast_to_static::<SInt<_>>(),
1.cast_to_static(),
OutputIntegerMode.Full64(),
false,
false,
@ -100,9 +103,9 @@ fn test_reg_alloc() {
flag_regs: [HdlNone(), HdlSome(())],
},
[2u8, 4u8],
0.cast_to_static::<SInt<_>>(),
0.cast_to_static(),
OutputIntegerMode.Full64(),
Lut4::from_fn(|a, b| a ^ b),
0b0110_hdl_u4,
),
];
let insns = insns_init.into_iter().chain(insns_loop.into_iter().cycle());

File diff suppressed because it is too large Load diff

View file

@ -1,199 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::test_cases::TestCase;
use cpu::{
decoder::simple_power_isa::decode_one_insn, instruction::MOp, util::array_vec::ArrayVec,
};
use fayalite::{prelude::*, sim::vcd::VcdWriterDecls, util::RcWriter};
use std::{fmt::Write as _, io::Write, process::Command};
mod test_cases;
#[test]
fn test_test_cases_assembly() -> std::io::Result<()> {
let llvm_mc_regex = regex::Regex::new(r"llvm-mc(-\d+)?$").expect("known to be a valid regex");
let llvm_mc = which::which_re(llvm_mc_regex)
.expect("can't find llvm-mc or llvm-mc-<num> in path")
.next()
.expect("can't find llvm-mc or llvm-mc-<num> in path");
let test_cases = test_cases::test_cases();
let mut assembly = String::new();
for TestCase {
mnemonic,
first_input: _,
second_input: _,
output: _,
loc: _,
} in &test_cases
{
writeln!(assembly, "{mnemonic}").unwrap();
}
let (reader, mut writer) = std::io::pipe()?;
let thread = std::thread::spawn(move || writer.write_all(assembly.as_bytes()));
let std::process::Output {
status,
stdout,
stderr,
} = Command::new(&llvm_mc)
.arg("--triple=powerpc64le-linux-gnu")
.arg("--assemble")
.arg("--filetype=asm")
.arg("--show-encoding")
.arg("-")
.stdin(reader)
.output()?;
let _ = thread.join();
let stderr = String::from_utf8_lossy(&stderr);
eprint!("{stderr}");
if !status.success() {
panic!("{} failed: {status}", llvm_mc.display());
}
let stdout = String::from_utf8_lossy(&stdout);
print!("{stdout}");
let mut lines = stdout.lines();
let text_line = lines.next();
assert_eq!(text_line, Some("\t.text"));
let mut any_error = false;
macro_rules! assert_eq_cont {
($l:expr, $r:expr, $($msg:tt)+) => {
match (&$l, &$r) {
(l, r) => if l != r {
eprintln!("assertion failed: {}\nl={l:#?}\nr={r:#?}", format_args!($($msg)+));
any_error = true;
}
}
};
}
for test_case @ TestCase {
mnemonic: _,
first_input,
second_input,
output: _,
loc: _,
} in test_cases
{
let Some(line) = lines.next() else {
panic!("output missing line for: {test_case:?}");
};
if line.starts_with("\t.long") {
assert_eq!(
line,
format!("\t.long\t{first_input}"),
"test_case={test_case:?}\nline:\n{line}"
);
if let Some(second_input) = second_input {
let Some(line) = lines.next() else {
panic!("output missing line for: {test_case:?}");
};
assert_eq!(
line,
format!("\t.long\t{second_input}"),
"test_case={test_case:?}\nline:\n{line}"
);
}
continue;
}
let Some((_, comment)) = line.split_once('#') else {
panic!("output line missing comment. test_case={test_case:?}\nline:\n{line}");
};
let [b0, b1, b2, b3] = first_input.to_le_bytes();
let expected_comment = if let Some(second_input) = second_input {
let [b4, b5, b6, b7] = second_input.to_le_bytes();
format!(
" encoding: [0x{b0:02x},0x{b1:02x},0x{b2:02x},0x{b3:02x},0x{b4:02x},0x{b5:02x},0x{b6:02x},0x{b7:02x}]"
)
} else {
format!(" encoding: [0x{b0:02x},0x{b1:02x},0x{b2:02x},0x{b3:02x}]")
};
assert_eq_cont!(
comment,
expected_comment,
"test_case={test_case:?}\nline:\n{line}"
);
}
for line in lines {
assert!(line.trim().is_empty(), "bad trailing output line: {line:?}");
}
if any_error {
panic!();
}
Ok(())
}
#[hdl]
#[test]
fn test_decode_insn() {
let _n = SourceLocation::normalize_files_for_tests();
let m = decode_one_insn();
let mut sim = Simulation::new(m);
let writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
struct DumpVcdOnDrop {
writer: Option<RcWriter>,
}
impl Drop for DumpVcdOnDrop {
fn drop(&mut self) {
if let Some(mut writer) = self.writer.take() {
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
}
}
}
let mut writer = DumpVcdOnDrop {
writer: Some(writer),
};
for test_case @ TestCase {
mnemonic: _,
first_input,
second_input,
output: _,
loc: _,
} in test_cases::test_cases()
{
sim.write(sim.io().first_input, first_input);
sim.write(sim.io().second_input, second_input);
sim.advance_time(SimDuration::from_micros(1));
let second_input_used = sim.read_bool(sim.io().second_input_used);
let is_illegal = sim.read_bool(sim.io().is_illegal);
let output = sim.read(sim.io().output);
#[derive(Debug)]
#[expect(dead_code, reason = "used only for Debug formatting")]
struct FormattedOutput<'a> {
insns: &'a [SimValue<MOp>],
second_input_used: bool,
is_illegal: bool,
}
let expected = format!(
"{:#?}",
FormattedOutput {
insns: ArrayVec::elements_sim_ref(&test_case.output),
second_input_used: second_input.is_some(),
is_illegal: false,
},
);
let output = format!(
"{:#?}",
FormattedOutput {
insns: ArrayVec::elements_sim_ref(&output),
second_input_used,
is_illegal,
},
);
assert!(
expected == output,
"test_case={test_case:#?}\noutput={output}\nexpected={expected}"
);
}
let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("expected/decode_one_insn.vcd") {
panic!();
}
}
#[hdl]
#[test]
fn test_simple_power_isa_decoder() {
// TODO
}

View file

@ -1,158 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use cpu::{instruction::MOp, util::array_vec::ArrayVec};
use fayalite::prelude::*;
use std::fmt;
mod branch;
mod condition_register;
mod fixed_point_arithmetic;
mod fixed_point_compare;
mod fixed_point_load;
mod fixed_point_logical;
mod fixed_point_rotate_and_shift;
mod fixed_point_store;
mod move_to_from_system_register;
mod prefixed_no_operation;
pub struct TestCase {
pub mnemonic: &'static str,
pub first_input: u32,
pub second_input: Option<u32>,
pub output: SimValue<ArrayVec<MOp, ConstUsize<3>>>,
pub loc: &'static std::panic::Location<'static>,
}
impl fmt::Debug for TestCase {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
mnemonic,
first_input,
second_input,
output,
loc,
} = self;
let mut debug_struct = f.debug_struct("TestCase");
debug_struct
.field("mnemonic", mnemonic)
.field("first_input", &format_args!("0x{first_input:08x}"));
if let Some(second_input) = second_input {
debug_struct.field("second_input", &format_args!("0x{second_input:08x}"));
} else {
debug_struct.field("second_input", &format_args!("None"));
}
debug_struct
.field("output", &ArrayVec::elements_sim_ref(output))
.field("loc", &format_args!("{loc}"))
.finish()
}
}
#[track_caller]
fn insn_empty(mnemonic: &'static str, first_input: u32, second_input: Option<u32>) -> TestCase {
let zero_mop = UInt::new_dyn(MOp.canonical().bit_width())
.zero()
.cast_bits_to(MOp);
TestCase {
mnemonic,
first_input,
second_input,
output: ArrayVec::new_sim(ArrayVec[MOp][ConstUsize], &zero_mop),
loc: std::panic::Location::caller(),
}
}
#[track_caller]
fn insn_single(
mnemonic: &'static str,
first_input: u32,
second_input: Option<u32>,
output: impl ToSimValue<Type = MOp>,
) -> TestCase {
let zero_mop = UInt::new_dyn(MOp.canonical().bit_width())
.zero()
.cast_bits_to(MOp);
let mut single_storage = ArrayVec::new_sim(ArrayVec[MOp][ConstUsize], &zero_mop);
ArrayVec::try_push_sim(&mut single_storage, zero_mop).expect("known to have space");
ArrayVec::elements_sim_mut(&mut single_storage)[0] = output.to_sim_value();
TestCase {
mnemonic,
first_input,
second_input,
output: single_storage,
loc: std::panic::Location::caller(),
}
}
#[track_caller]
fn insn_double(
mnemonic: &'static str,
first_input: u32,
second_input: Option<u32>,
insns: [impl ToSimValue<Type = MOp>; 2],
) -> TestCase {
let zero_mop = UInt::new_dyn(MOp.canonical().bit_width())
.zero()
.cast_bits_to(MOp);
let mut single_storage = ArrayVec::new_sim(ArrayVec[MOp][ConstUsize], &zero_mop);
ArrayVec::try_push_sim(&mut single_storage, &zero_mop).expect("known to have space");
ArrayVec::try_push_sim(&mut single_storage, zero_mop).expect("known to have space");
ArrayVec::elements_sim_mut(&mut single_storage)[0] = insns[0].to_sim_value();
ArrayVec::elements_sim_mut(&mut single_storage)[1] = insns[1].to_sim_value();
TestCase {
mnemonic,
first_input,
second_input,
output: single_storage,
loc: std::panic::Location::caller(),
}
}
#[track_caller]
fn insn_triple(
mnemonic: &'static str,
first_input: u32,
second_input: Option<u32>,
insns: [impl ToSimValue<Type = MOp>; 3],
) -> TestCase {
let zero_mop = UInt::new_dyn(MOp.canonical().bit_width())
.zero()
.cast_bits_to(MOp);
let mut single_storage = ArrayVec::new_sim(ArrayVec[MOp][ConstUsize], &zero_mop);
ArrayVec::try_push_sim(&mut single_storage, &zero_mop).expect("known to have space");
ArrayVec::try_push_sim(&mut single_storage, &zero_mop).expect("known to have space");
ArrayVec::try_push_sim(&mut single_storage, zero_mop).expect("known to have space");
ArrayVec::elements_sim_mut(&mut single_storage)[0] = insns[0].to_sim_value();
ArrayVec::elements_sim_mut(&mut single_storage)[1] = insns[1].to_sim_value();
ArrayVec::elements_sim_mut(&mut single_storage)[2] = insns[2].to_sim_value();
TestCase {
mnemonic,
first_input,
second_input,
output: single_storage,
loc: std::panic::Location::caller(),
}
}
pub fn test_cases() -> Vec<TestCase> {
let mut retval = Vec::new();
branch::test_cases_book_i_2_4_branch(&mut retval);
condition_register::test_cases_book_i_2_5_condition_register(&mut retval);
fixed_point_load::test_cases_book_i_3_3_2_fixed_point_load(&mut retval);
fixed_point_store::test_cases_book_i_3_3_3_fixed_point_store(&mut retval);
fixed_point_arithmetic::test_cases_book_i_3_3_9_fixed_point_arithmetic(&mut retval);
fixed_point_compare::test_cases_book_i_3_3_10_fixed_point_compare(&mut retval);
fixed_point_logical::test_cases_book_i_3_3_13_fixed_point_logical(&mut retval);
fixed_point_rotate_and_shift::test_cases_book_i_3_3_14_fixed_point_rotate_and_shift(
&mut retval,
);
move_to_from_system_register::test_cases_book_i_3_3_19_move_to_from_system_register(
&mut retval,
);
prefixed_no_operation::test_cases_book_i_3_3_20_prefixed_no_operation(&mut retval);
move_to_from_system_register::test_cases_book_iii_5_4_4_move_to_from_system_register(
&mut retval,
);
retval
}

View file

@ -1,452 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::test_cases::{TestCase, insn_double, insn_single};
use cpu::instruction::{
AddSubMOp, BranchMOp, ConditionMode, MOp, MOpDestReg, MOpRegNum, OutputIntegerMode,
};
use fayalite::prelude::*;
/// covers instructions in PowerISA v3.1C Book I 2.4 Branch Instructions
pub fn test_cases_book_i_2_4_branch(retval: &mut Vec<TestCase>) {
retval.push(insn_single(
"b 0x345678",
0x48345678,
None,
BranchMOp::branch_i(
MOpDestReg::new_sim(&[], &[]),
[MOpRegNum::const_zero(); 2],
0x345678.cast_to_static::<SInt<_>>(),
true,
false,
false,
),
));
retval.push(insn_single(
"ba 0x345678",
0x4834567a,
None,
BranchMOp::branch_i(
MOpDestReg::new_sim(&[], &[]),
[MOpRegNum::const_zero(); 2],
0x345678.cast_to_static::<SInt<_>>(),
false,
false,
false,
),
));
retval.push(insn_single(
"bl 0x345678",
0x48345679,
None,
BranchMOp::branch_i(
MOpDestReg::new_sim(&[MOpRegNum::POWER_ISA_LR_REG_NUM], &[]),
[MOpRegNum::const_zero(); 2],
0x345678.cast_to_static::<SInt<_>>(),
true,
true,
false,
),
));
retval.push(insn_single(
"bla 0x345678",
0x4834567b,
None,
BranchMOp::branch_i(
MOpDestReg::new_sim(&[MOpRegNum::POWER_ISA_LR_REG_NUM], &[]),
[MOpRegNum::const_zero(); 2],
0x345678.cast_to_static::<SInt<_>>(),
false,
true,
false,
),
));
fn insn_dec_ctr_and(
mnemonic: &'static str,
first_input: u32,
second_input: Option<u32>,
second_insn: impl ToSimValue<Type = MOp>,
) -> TestCase {
insn_double(
mnemonic,
first_input,
second_input,
[
AddSubMOp::add_sub_i::<MOp>(
MOpDestReg::new([MOpRegNum::power_isa_ctr_reg()], []),
[MOpRegNum::power_isa_ctr_reg(), MOpRegNum::const_zero()],
(-1).cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
false,
false,
false,
)
.into_sim_value(),
second_insn.into_sim_value(),
],
)
}
macro_rules! insn_branch_conds {
(
mnemonic = $mnemonic:literal;
mnemonic_l = $mnemonic_l:literal;
asm_last_arg = $asm_last_arg:literal;
imm = $imm:literal;
encoding = $encoding:literal;
src1 = $src1:expr;
pc_relative = $pc_relative:expr;
is_ret = $is_ret:expr;
) => {
insn_branch_conds! {
mnemonic = $mnemonic;
asm_last_arg = $asm_last_arg;
imm = $imm;
encoding = $encoding;
dest = MOpDestReg::new_sim(&[], &[]);
src1 = $src1;
pc_relative = $pc_relative;
lk = false;
is_ret = $is_ret;
}
insn_branch_conds! {
mnemonic = $mnemonic_l;
asm_last_arg = $asm_last_arg;
imm = $imm;
encoding = $encoding | 1;
dest = MOpDestReg::new_sim(&[MOpRegNum::POWER_ISA_LR_REG_NUM], &[]);
src1 = $src1;
pc_relative = $pc_relative;
lk = true;
is_ret = $is_ret;
}
};
(
mnemonic = $mnemonic:literal;
asm_last_arg = $asm_last_arg:literal;
imm = $imm:literal;
encoding = $encoding:expr;
dest = $dest:expr;
src1 = $src1:expr;
pc_relative = $pc_relative:expr;
lk = $lk:expr;
is_ret = $is_ret:expr;
) => {
if !$mnemonic.starts_with("bcctr") {
retval.push(insn_dec_ctr_and(
concat!($mnemonic, " 0, 0, ", $asm_last_arg),
$encoding,
None,
BranchMOp::branch_cond_ctr(
$dest,
[
MOpRegNum::power_isa_cr_reg_imm(0),
$src1,
MOpRegNum::power_isa_ctr_reg(),
],
$imm.cast_to_static::<SInt<_>>(),
true,
ConditionMode.SLt(),
true,
$pc_relative,
$lk,
$is_ret,
),
));
retval.push(insn_dec_ctr_and(
concat!($mnemonic, " 0, 1, ", $asm_last_arg),
$encoding | 0x010000,
None,
BranchMOp::branch_cond_ctr(
$dest,
[
MOpRegNum::power_isa_cr_reg_imm(0),
$src1,
MOpRegNum::power_isa_ctr_reg(),
],
$imm.cast_to_static::<SInt<_>>(),
true,
ConditionMode.SGt(),
true,
$pc_relative,
$lk,
$is_ret,
),
));
retval.push(insn_dec_ctr_and(
concat!($mnemonic, " 0, 2, ", $asm_last_arg),
$encoding | 0x020000,
None,
BranchMOp::branch_cond_ctr(
$dest,
[
MOpRegNum::power_isa_cr_reg_imm(0),
$src1,
MOpRegNum::power_isa_ctr_reg(),
],
$imm.cast_to_static::<SInt<_>>(),
true,
ConditionMode.Eq(),
true,
$pc_relative,
$lk,
$is_ret,
),
));
retval.push(insn_dec_ctr_and(
concat!($mnemonic, " 0, 3, ", $asm_last_arg),
$encoding | 0x030000,
None,
BranchMOp::branch_cond_ctr(
$dest,
[
MOpRegNum::power_isa_cr_reg_imm(0),
$src1,
MOpRegNum::power_isa_ctr_reg(),
],
$imm.cast_to_static::<SInt<_>>(),
true,
ConditionMode.Overflow(),
true,
$pc_relative,
$lk,
$is_ret,
),
));
retval.push(insn_dec_ctr_and(
concat!($mnemonic, " 0, 9, ", $asm_last_arg),
$encoding | 0x090000,
None,
BranchMOp::branch_cond_ctr(
$dest,
[
MOpRegNum::power_isa_cr_reg_imm(2),
$src1,
MOpRegNum::power_isa_ctr_reg(),
],
$imm.cast_to_static::<SInt<_>>(),
true,
ConditionMode.SGt(),
true,
$pc_relative,
$lk,
$is_ret,
),
));
retval.push(insn_dec_ctr_and(
concat!($mnemonic, " 2, 0, ", $asm_last_arg),
$encoding | (2 << 21),
None,
BranchMOp::branch_cond_ctr(
$dest,
[
MOpRegNum::power_isa_cr_reg_imm(0),
$src1,
MOpRegNum::power_isa_ctr_reg(),
],
$imm.cast_to_static::<SInt<_>>(),
true,
ConditionMode.SLt(),
false,
$pc_relative,
$lk,
$is_ret,
),
));
}
retval.push(insn_single(
concat!($mnemonic, " 4, 0, ", $asm_last_arg),
$encoding | (4 << 21),
None,
BranchMOp::branch_cond_ctr(
$dest,
[
MOpRegNum::power_isa_cr_reg_imm(0),
$src1,
MOpRegNum::const_zero(),
],
$imm.cast_to_static::<SInt<_>>(),
true,
ConditionMode.SLt(),
true,
$pc_relative,
$lk,
$is_ret,
),
));
if !$mnemonic.starts_with("bcctr") {
retval.push(insn_dec_ctr_and(
concat!($mnemonic, " 8, 0, ", $asm_last_arg),
$encoding | (8 << 21),
None,
BranchMOp::branch_cond_ctr(
$dest,
[
MOpRegNum::power_isa_cr_reg_imm(0),
$src1,
MOpRegNum::power_isa_ctr_reg(),
],
$imm.cast_to_static::<SInt<_>>(),
false,
ConditionMode.SLt(),
true,
$pc_relative,
$lk,
$is_ret,
),
));
retval.push(insn_dec_ctr_and(
concat!($mnemonic, " 10, 0, ", $asm_last_arg),
$encoding | (10 << 21),
None,
BranchMOp::branch_cond_ctr(
$dest,
[
MOpRegNum::power_isa_cr_reg_imm(0),
$src1,
MOpRegNum::power_isa_ctr_reg(),
],
$imm.cast_to_static::<SInt<_>>(),
false,
ConditionMode.SLt(),
false,
$pc_relative,
$lk,
$is_ret,
),
));
}
retval.push(insn_single(
concat!($mnemonic, " 12, 0, ", $asm_last_arg),
$encoding | (12 << 21),
None,
BranchMOp::branch_cond_ctr(
$dest,
[
MOpRegNum::power_isa_cr_reg_imm(0),
$src1,
MOpRegNum::const_zero(),
],
$imm.cast_to_static::<SInt<_>>(),
false,
ConditionMode.SLt(),
true,
$pc_relative,
$lk,
$is_ret,
),
));
if !$mnemonic.starts_with("bcctr") {
retval.push(insn_dec_ctr_and(
concat!($mnemonic, " 16, 0, ", $asm_last_arg),
$encoding | (16 << 21),
None,
BranchMOp::branch_ctr(
$dest,
[
MOpRegNum::const_zero(),
$src1,
MOpRegNum::power_isa_ctr_reg(),
],
$imm.cast_to_static::<SInt<_>>(),
true,
$pc_relative,
$lk,
$is_ret,
),
));
retval.push(insn_dec_ctr_and(
concat!($mnemonic, " 18, 0, ", $asm_last_arg),
$encoding | (18 << 21),
None,
BranchMOp::branch_ctr(
$dest,
[
MOpRegNum::const_zero(),
$src1,
MOpRegNum::power_isa_ctr_reg(),
],
$imm.cast_to_static::<SInt<_>>(),
false,
$pc_relative,
$lk,
$is_ret,
),
));
}
retval.push(insn_single(
concat!($mnemonic, " 20, 0, ", $asm_last_arg),
$encoding | (20 << 21),
None,
BranchMOp::branch_i(
$dest,
[MOpRegNum::const_zero(), $src1],
$imm.cast_to_static::<SInt<_>>(),
$pc_relative,
$lk,
$is_ret,
),
));
};
}
insn_branch_conds! {
mnemonic = "bc";
mnemonic_l = "bcl";
asm_last_arg = "0x1234";
imm = 0x1234;
encoding = 0x40001234;
src1 = MOpRegNum::const_zero();
pc_relative = true;
is_ret = false;
}
insn_branch_conds! {
mnemonic = "bca";
mnemonic_l = "bcla";
asm_last_arg = "0x1234";
imm = 0x1234;
encoding = 0x40001236;
src1 = MOpRegNum::const_zero();
pc_relative = false;
is_ret = false;
}
insn_branch_conds! {
mnemonic = "bclr";
mnemonic_l = "bclrl";
asm_last_arg = "0";
imm = 0;
encoding = 0x4c000020;
src1 = MOpRegNum::power_isa_lr_reg();
pc_relative = false;
is_ret = true;
}
insn_branch_conds! {
mnemonic = "bcctr";
mnemonic_l = "bcctrl";
asm_last_arg = "0";
imm = 0;
encoding = 0x4c000420;
src1 = MOpRegNum::power_isa_ctr_reg();
pc_relative = false;
is_ret = false;
}
retval.push(insn_dec_ctr_and(
// LLVM doesn't support the bctar[l] instructions:
// https://github.com/llvm/llvm-project/issues/176864
".long 0x4e400461 # bctarl 18, 0, 0",
0x4e400461,
None,
BranchMOp::branch_ctr(
MOpDestReg::new_sim(&[MOpRegNum::POWER_ISA_LR_REG_NUM], &[]),
[
MOpRegNum::const_zero(),
MOpRegNum::power_isa_tar_reg(),
MOpRegNum::power_isa_ctr_reg(),
],
0.cast_to_static::<SInt<_>>(),
false,
false,
true,
false,
),
));
}

View file

@ -1,103 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::test_cases::{TestCase, insn_single};
use cpu::{
instruction::{LogicalFlagsMOp, LogicalFlagsMOpImm, Lut4, MOpDestReg, MOpRegNum, MoveRegMOp},
register::PRegFlagsPowerISA,
};
use fayalite::prelude::*;
/// covers instructions in PowerISA v3.1C Book I 2.5 Condition Register Instructions
pub fn test_cases_book_i_2_5_condition_register(retval: &mut Vec<TestCase>) {
macro_rules! cr_bit_logical_op {
(
$mnemonic:literal,
$encoding:literal,
$lut:expr
) => {{
retval.push(insn_single(
concat!($mnemonic, " 4*cr3+so, 4*cr1+gt, 4*cr5+lt"),
$encoding | 0x01e5a000,
None,
LogicalFlagsMOp::logical_flags(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_cr_reg_num(3)], &[]),
[
MOpRegNum::power_isa_cr_reg_imm(1),
MOpRegNum::power_isa_cr_reg_imm(5),
MOpRegNum::power_isa_cr_reg_imm(3),
],
LogicalFlagsMOpImm::from_swizzle_fn::<PRegFlagsPowerISA>(|src0, src1, src2| {
let mut dest = src2.map(|v| Some(v.into()));
dest.so = Some((src0.cr_gt, src1.cr_lt).into());
dest
}),
$lut,
),
));
retval.push(insn_single(
concat!($mnemonic, " lt, gt, eq"),
$encoding | 0x00011000,
None,
LogicalFlagsMOp::logical_flags(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_cr_reg_num(0)], &[]),
[
MOpRegNum::power_isa_cr_reg_imm(0),
MOpRegNum::power_isa_cr_reg_imm(0),
MOpRegNum::power_isa_cr_reg_imm(0),
],
LogicalFlagsMOpImm::from_swizzle_fn::<PRegFlagsPowerISA>(|src0, src1, src2| {
let mut dest = src2.map(|v| Some(v.into()));
dest.cr_lt = Some((src0.cr_gt, src1.cr_eq).into());
dest
}),
$lut,
),
));
retval.push(insn_single(
concat!($mnemonic, " gt, gt, eq"),
$encoding | 0x00211000,
None,
LogicalFlagsMOp::logical_flags(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_cr_reg_num(0)], &[]),
[
MOpRegNum::power_isa_cr_reg_imm(0),
MOpRegNum::power_isa_cr_reg_imm(0),
MOpRegNum::power_isa_cr_reg_imm(0),
],
LogicalFlagsMOpImm::from_swizzle_fn::<PRegFlagsPowerISA>(|src0, src1, src2| {
let mut dest = src2.map(|v| Some(v.into()));
dest.cr_gt = Some((src0.cr_gt, src1.cr_eq).into());
dest
}),
$lut,
),
));
}};
}
cr_bit_logical_op!("crand", 0x4c000202, Lut4::from_fn(|a, b| a & b));
cr_bit_logical_op!("crnand", 0x4c0001c2, Lut4::from_fn(|a, b| !(a & b)));
cr_bit_logical_op!("cror", 0x4c000382, Lut4::from_fn(|a, b| a | b));
cr_bit_logical_op!("crxor", 0x4c000182, Lut4::from_fn(|a, b| a ^ b));
cr_bit_logical_op!("crnor", 0x4c000042, Lut4::from_fn(|a, b| !(a | b)));
cr_bit_logical_op!("creqv", 0x4c000242, Lut4::from_fn(|a, b| a == b));
cr_bit_logical_op!("crandc", 0x4c000102, Lut4::from_fn(|a, b| a & !b));
cr_bit_logical_op!("crorc", 0x4c000342, Lut4::from_fn(|a, b| a | !b));
macro_rules! mcrf {
($dest:literal, $src:literal; $encoding:literal) => {
retval.push(insn_single(
concat!("mcrf ", $dest, ", ", $src),
$encoding,
None,
MoveRegMOp::move_reg(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_cr_reg_num($dest)], &[]),
[MOpRegNum::power_isa_cr_reg_imm($src)],
0i8.cast_to_static::<SInt<_>>(),
),
));
};
}
mcrf!(0, 0; 0x4c000000);
mcrf!(5, 7; 0x4e9c0000);
mcrf!(5, 0; 0x4e800000);
}

View file

@ -1,393 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::test_cases::{TestCase, insn_single};
use cpu::instruction::{AddSubMOp, MOpDestReg, MOpRegNum, OutputIntegerMode};
use fayalite::prelude::*;
/// covers instructions in PowerISA v3.1C Book I 3.3.9 Fixed-Point Arithmetic Instructions
pub fn test_cases_book_i_3_3_9_fixed_point_arithmetic(retval: &mut Vec<TestCase>) {
retval.push(insn_single(
"addi 3, 4, 0x1234",
0x38641234,
None,
AddSubMOp::add_sub_i(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(3)], &[]),
[MOpRegNum::power_isa_gpr_reg_imm(4), MOpRegNum::const_zero()],
0x1234.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
false,
false,
false,
),
));
retval.push(insn_single(
"paddi 3, 4, 0x123456789, 0",
0x06012345,
Some(0x38646789),
AddSubMOp::add_sub_i(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(3)], &[]),
[MOpRegNum::power_isa_gpr_reg_imm(4), MOpRegNum::const_zero()],
0x123456789i64.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
false,
false,
false,
),
));
retval.push(insn_single(
"paddi 3, 0, 0x123456789, 1",
0x06112345,
Some(0x38606789),
AddSubMOp::add_sub_i(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(3)], &[]),
[MOpRegNum::const_zero(), MOpRegNum::const_zero()],
0x123456789i64.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
false,
false,
true,
),
));
retval.push(insn_single(
"addis 3, 4, 0x1234",
0x3C641234,
None,
AddSubMOp::add_sub_i(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(3)], &[]),
[MOpRegNum::power_isa_gpr_reg_imm(4), MOpRegNum::const_zero()],
0x12340000.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
false,
false,
false,
),
));
retval.push(insn_single(
"addpcis 3, 0x1234",
0x4c7a1204,
None,
AddSubMOp::add_sub_i(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(3)], &[]),
[MOpRegNum::const_zero(); _],
0x12340004.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
false,
false,
true,
),
));
retval.push(insn_single(
"add. 3, 4, 5",
0x7c642a15,
None,
AddSubMOp::add_sub(
MOpDestReg::new_sim(
&[MOpRegNum::power_isa_gpr_reg_num(3)],
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM],
),
[
MOpRegNum::power_isa_gpr_reg_imm(4),
MOpRegNum::power_isa_gpr_reg_imm(5),
MOpRegNum::const_zero(),
],
0.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
false,
false,
false,
),
));
retval.push(insn_single(
"addic. 3, 4, 0x1234",
0x34641234,
None,
AddSubMOp::add_sub_i(
MOpDestReg::new_sim(
&[
MOpRegNum::power_isa_gpr_reg_num(3),
MOpRegNum::POWER_ISA_XER_CA_CA32_REG_NUM,
],
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM],
),
[MOpRegNum::power_isa_gpr_reg_imm(4), MOpRegNum::const_zero()],
0x1234.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
false,
false,
false,
),
));
retval.push(insn_single(
"subf. 3, 4, 5",
0x7c642851,
None,
AddSubMOp::add_sub(
MOpDestReg::new_sim(
&[MOpRegNum::power_isa_gpr_reg_num(3)],
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM],
),
[
MOpRegNum::power_isa_gpr_reg_imm(4),
MOpRegNum::power_isa_gpr_reg_imm(5),
MOpRegNum::const_zero(),
],
0.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
true,
false,
true,
false,
),
));
retval.push(insn_single(
"subfic 3, 4, 0x1234",
0x20641234,
None,
AddSubMOp::add_sub_i(
MOpDestReg::new_sim(
&[
MOpRegNum::power_isa_gpr_reg_num(3),
MOpRegNum::POWER_ISA_XER_CA_CA32_REG_NUM,
],
&[],
),
[MOpRegNum::power_isa_gpr_reg_imm(4), MOpRegNum::const_zero()],
0x1234.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
true,
false,
true,
false,
),
));
retval.push(insn_single(
"addc. 3, 4, 5",
0x7c642815,
None,
AddSubMOp::add_sub(
MOpDestReg::new_sim(
&[
MOpRegNum::power_isa_gpr_reg_num(3),
MOpRegNum::POWER_ISA_XER_CA_CA32_REG_NUM,
],
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM],
),
[
MOpRegNum::power_isa_gpr_reg_imm(4),
MOpRegNum::power_isa_gpr_reg_imm(5),
MOpRegNum::const_zero(),
],
0.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
false,
false,
false,
),
));
retval.push(insn_single(
"subfc. 3, 4, 5",
0x7c642811,
None,
AddSubMOp::add_sub(
MOpDestReg::new_sim(
&[
MOpRegNum::power_isa_gpr_reg_num(3),
MOpRegNum::POWER_ISA_XER_CA_CA32_REG_NUM,
],
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM],
),
[
MOpRegNum::power_isa_gpr_reg_imm(4),
MOpRegNum::power_isa_gpr_reg_imm(5),
MOpRegNum::const_zero(),
],
0.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
true,
false,
true,
false,
),
));
retval.push(insn_single(
"adde. 3, 4, 5",
0x7c642915,
None,
AddSubMOp::add_sub(
MOpDestReg::new_sim(
&[
MOpRegNum::power_isa_gpr_reg_num(3),
MOpRegNum::POWER_ISA_XER_CA_CA32_REG_NUM,
],
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM],
),
[
MOpRegNum::power_isa_gpr_reg_imm(4),
MOpRegNum::power_isa_xer_ca_ca32_reg(),
MOpRegNum::power_isa_gpr_reg_imm(5),
],
0.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
true,
false,
false,
),
));
retval.push(insn_single(
"subfe. 3, 4, 5",
0x7c642911,
None,
AddSubMOp::add_sub(
MOpDestReg::new_sim(
&[
MOpRegNum::power_isa_gpr_reg_num(3),
MOpRegNum::POWER_ISA_XER_CA_CA32_REG_NUM,
],
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM],
),
[
MOpRegNum::power_isa_gpr_reg_imm(4),
MOpRegNum::power_isa_xer_ca_ca32_reg(),
MOpRegNum::power_isa_gpr_reg_imm(5),
],
0.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
true,
true,
false,
false,
),
));
retval.push(insn_single(
"addme. 3, 4",
0x7c6401d5,
None,
AddSubMOp::add_sub(
MOpDestReg::new_sim(
&[
MOpRegNum::power_isa_gpr_reg_num(3),
MOpRegNum::POWER_ISA_XER_CA_CA32_REG_NUM,
],
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM],
),
[
MOpRegNum::power_isa_gpr_reg_imm(4),
MOpRegNum::power_isa_xer_ca_ca32_reg(),
MOpRegNum::const_zero(),
],
(-1i8).cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
true,
false,
false,
),
));
retval.push(insn_single(
"subfme. 3, 4",
0x7c6401d1,
None,
AddSubMOp::add_sub(
MOpDestReg::new_sim(
&[
MOpRegNum::power_isa_gpr_reg_num(3),
MOpRegNum::POWER_ISA_XER_CA_CA32_REG_NUM,
],
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM],
),
[
MOpRegNum::power_isa_gpr_reg_imm(4),
MOpRegNum::power_isa_xer_ca_ca32_reg(),
MOpRegNum::const_zero(),
],
(-1i8).cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
true,
true,
false,
false,
),
));
retval.push(insn_single(
"addze. 3, 4",
0x7c640195,
None,
AddSubMOp::add_sub(
MOpDestReg::new_sim(
&[
MOpRegNum::power_isa_gpr_reg_num(3),
MOpRegNum::POWER_ISA_XER_CA_CA32_REG_NUM,
],
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM],
),
[
MOpRegNum::power_isa_gpr_reg_imm(4),
MOpRegNum::power_isa_xer_ca_ca32_reg(),
MOpRegNum::const_zero(),
],
0.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
true,
false,
false,
),
));
retval.push(insn_single(
"subfze. 3, 4",
0x7c640191,
None,
AddSubMOp::add_sub(
MOpDestReg::new_sim(
&[
MOpRegNum::power_isa_gpr_reg_num(3),
MOpRegNum::POWER_ISA_XER_CA_CA32_REG_NUM,
],
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM],
),
[
MOpRegNum::power_isa_gpr_reg_imm(4),
MOpRegNum::power_isa_xer_ca_ca32_reg(),
MOpRegNum::const_zero(),
],
0.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
true,
true,
false,
false,
),
));
retval.push(insn_single(
"neg. 3, 4",
0x7c6400d1,
None,
AddSubMOp::add_sub(
MOpDestReg::new_sim(
&[MOpRegNum::power_isa_gpr_reg_num(3)],
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM],
),
[
MOpRegNum::power_isa_gpr_reg_imm(4),
MOpRegNum::const_zero(),
MOpRegNum::const_zero(),
],
0.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
true,
false,
true,
false,
),
));
}

View file

@ -1,145 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::test_cases::{TestCase, insn_single};
use cpu::instruction::{CompareMOp, CompareMode, MOpDestReg, MOpRegNum};
use fayalite::prelude::*;
/// covers instructions in PowerISA v3.1C Book I 3.3.10 Fixed-Point Compare Instructions
pub fn test_cases_book_i_3_3_10_fixed_point_compare(retval: &mut Vec<TestCase>) {
retval.push(insn_single(
"cmpi 3, 0, 4, 0x1234",
0x2d841234,
None,
CompareMOp::compare_i(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_cr_reg_num(3)], &[]),
[MOpRegNum::power_isa_gpr_reg_imm(4)],
0x1234.cast_to_static::<SInt<_>>(),
CompareMode.S32(),
),
));
retval.push(insn_single(
"cmpi 3, 1, 4, -0x7655",
0x2da489ab,
None,
CompareMOp::compare_i(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_cr_reg_num(3)], &[]),
[MOpRegNum::power_isa_gpr_reg_imm(4)],
(0x89abu16 as i16).cast_to_static::<SInt<_>>(),
CompareMode.S64(),
),
));
retval.push(insn_single(
"cmp 3, 0, 4, 5",
0x7d842800,
None,
CompareMOp::compare(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_cr_reg_num(3)], &[]),
[
MOpRegNum::power_isa_gpr_reg_imm(4),
MOpRegNum::power_isa_gpr_reg_imm(5),
],
CompareMode.S32(),
),
));
retval.push(insn_single(
"cmp 3, 1, 4, 5",
0x7da42800,
None,
CompareMOp::compare(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_cr_reg_num(3)], &[]),
[
MOpRegNum::power_isa_gpr_reg_imm(4),
MOpRegNum::power_isa_gpr_reg_imm(5),
],
CompareMode.S64(),
),
));
retval.push(insn_single(
"cmpli 3, 0, 4, 0x1234",
0x29841234,
None,
CompareMOp::compare_i(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_cr_reg_num(3)], &[]),
[MOpRegNum::power_isa_gpr_reg_imm(4)],
0x1234.cast_to_static::<SInt<_>>(),
CompareMode.U32(),
),
));
retval.push(insn_single(
"cmpli 3, 1, 4, 0x89ab",
0x29a489ab,
None,
CompareMOp::compare_i(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_cr_reg_num(3)], &[]),
[MOpRegNum::power_isa_gpr_reg_imm(4)],
0x89ab.cast_to_static::<SInt<_>>(),
CompareMode.U64(),
),
));
retval.push(insn_single(
"cmpl 3, 0, 4, 5",
0x7d842840,
None,
CompareMOp::compare(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_cr_reg_num(3)], &[]),
[
MOpRegNum::power_isa_gpr_reg_imm(4),
MOpRegNum::power_isa_gpr_reg_imm(5),
],
CompareMode.U32(),
),
));
retval.push(insn_single(
"cmpl 3, 1, 4, 5",
0x7da42840,
None,
CompareMOp::compare(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_cr_reg_num(3)], &[]),
[
MOpRegNum::power_isa_gpr_reg_imm(4),
MOpRegNum::power_isa_gpr_reg_imm(5),
],
CompareMode.U64(),
),
));
retval.push(insn_single(
"cmprb 3, 0, 4, 5",
0x7d842980,
None,
CompareMOp::compare(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_cr_reg_num(3)], &[]),
[
MOpRegNum::power_isa_gpr_reg_imm(4),
MOpRegNum::power_isa_gpr_reg_imm(5),
],
CompareMode.CmpRBOne(),
),
));
retval.push(insn_single(
"cmprb 3, 1, 4, 5",
0x7da42980,
None,
CompareMOp::compare(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_cr_reg_num(3)], &[]),
[
MOpRegNum::power_isa_gpr_reg_imm(4),
MOpRegNum::power_isa_gpr_reg_imm(5),
],
CompareMode.CmpRBTwo(),
),
));
retval.push(insn_single(
"cmpeqb 3, 4, 5",
0x7d8429c0,
None,
CompareMOp::compare(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_cr_reg_num(3)], &[]),
[
MOpRegNum::power_isa_gpr_reg_imm(4),
MOpRegNum::power_isa_gpr_reg_imm(5),
],
CompareMode.CmpEqB(),
),
));
}

View file

@ -1,525 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::test_cases::{TestCase, insn_double};
use cpu::instruction::{
AddSubMOp, LoadMOp, LoadStoreConversion, LoadStoreWidth, MOpDestReg, MOpRegNum,
OutputIntegerMode,
};
use fayalite::prelude::*;
/// covers instructions in PowerISA v3.1C Book I 3.3.2 Fixed-Point Load Instructions
pub fn test_cases_book_i_3_3_2_fixed_point_load(retval: &mut Vec<TestCase>) {
macro_rules! load_prefixed {
(
$mnemonic:literal $dest:literal, $disp:literal($ra:literal), $r:literal;
$prefix:literal, $suffix:literal;
$width:ident;
$conversion:ident;
) => {
retval.push(insn_double(
concat!($mnemonic, " ", $dest, ", ", $disp, "(", $ra, "), ", $r),
$prefix,
Some($suffix),
[
AddSubMOp::add_sub_i(
MOpDestReg::new_sim(&[MOpRegNum::POWER_ISA_TEMP_REG_NUM], &[]),
[
if $r != 0 || $ra == 0 {
MOpRegNum::const_zero()
} else {
MOpRegNum::power_isa_gpr_reg_imm($ra)
},
MOpRegNum::const_zero(),
],
($disp as i64).cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
false,
false,
$r != 0,
),
LoadMOp::load(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num($dest)], &[]),
[MOpRegNum::power_isa_temp_reg()],
LoadStoreWidth.$width(),
LoadStoreConversion.$conversion(),
),
],
));
};
}
macro_rules! load {
(
$mnemonic:literal $dest:literal, $disp:literal($ra:literal);
$encoding:literal;
$width:ident;
$conversion:ident;
) => {
retval.push(insn_double(
concat!($mnemonic, " ", $dest, ", ", $disp, "(", $ra, ")"),
$encoding,
None,
[
AddSubMOp::add_sub_i(
MOpDestReg::new_sim(&[MOpRegNum::POWER_ISA_TEMP_REG_NUM], &[]),
[
if $ra == 0 {
MOpRegNum::const_zero()
} else {
MOpRegNum::power_isa_gpr_reg_imm($ra)
},
MOpRegNum::const_zero(),
],
($disp as i64).cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
false,
false,
false,
),
LoadMOp::load(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num($dest)], &[]),
[MOpRegNum::power_isa_temp_reg()],
LoadStoreWidth.$width(),
LoadStoreConversion.$conversion(),
),
],
));
};
}
macro_rules! load_update {
(
$mnemonic:literal $dest:literal, $disp:literal($ra:literal);
$encoding:literal;
$width:ident;
$conversion:ident;
) => {
retval.push(insn_double(
concat!($mnemonic, " ", $dest, ", ", $disp, "(", $ra, ")"),
$encoding,
None,
[
AddSubMOp::add_sub_i(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num($ra)], &[]),
[
MOpRegNum::power_isa_gpr_reg_imm($ra),
MOpRegNum::const_zero(),
],
($disp as i64).cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
false,
false,
false,
),
LoadMOp::load(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num($dest)], &[]),
[MOpRegNum::power_isa_gpr_reg_imm($ra)],
LoadStoreWidth.$width(),
LoadStoreConversion.$conversion(),
),
],
));
};
}
macro_rules! load_indexed {
(
$mnemonic:literal $dest:literal, $ra:literal, $rb:literal;
$encoding:literal;
$width:ident;
$conversion:ident;
) => {
retval.push(insn_double(
concat!($mnemonic, " ", $dest, ", ", $ra, ", ", $rb),
$encoding,
None,
[
AddSubMOp::add_sub_i(
MOpDestReg::new_sim(&[MOpRegNum::POWER_ISA_TEMP_REG_NUM], &[]),
[
if $ra == 0 {
MOpRegNum::const_zero()
} else {
MOpRegNum::power_isa_gpr_reg_imm($ra)
},
MOpRegNum::power_isa_gpr_reg_imm($rb),
],
0.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
false,
false,
false,
),
LoadMOp::load(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num($dest)], &[]),
[MOpRegNum::power_isa_temp_reg()],
LoadStoreWidth.$width(),
LoadStoreConversion.$conversion(),
),
],
));
};
}
macro_rules! load_update_indexed {
(
$mnemonic:literal $dest:literal, $ra:literal, $rb:literal;
$encoding:literal;
$width:ident;
$conversion:ident;
) => {
retval.push(insn_double(
concat!($mnemonic, " ", $dest, ", ", $ra, ", ", $rb),
$encoding,
None,
[
AddSubMOp::add_sub_i(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num($ra)], &[]),
[
MOpRegNum::power_isa_gpr_reg_imm($ra),
MOpRegNum::power_isa_gpr_reg_imm($rb),
],
0.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
false,
false,
false,
),
LoadMOp::load(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num($dest)], &[]),
[MOpRegNum::power_isa_gpr_reg_imm($ra)],
LoadStoreWidth.$width(),
LoadStoreConversion.$conversion(),
),
],
));
};
}
load! {
"lbz" 3, 0x1234(4);
0x88641234;
Width8Bit;
ZeroExt;
}
load! {
"lbz" 3, 0x1234(0);
0x88601234;
Width8Bit;
ZeroExt;
}
load_prefixed! {
"plbz" 3, 0x123456789(4), 0;
0x06012345, 0x88646789;
Width8Bit;
ZeroExt;
}
load_prefixed! {
"plbz" 3, 0x123456789(0), 0;
0x06012345, 0x88606789;
Width8Bit;
ZeroExt;
}
load_prefixed! {
"plbz" 3, 0x123456789(0), 1;
0x06112345, 0x88606789;
Width8Bit;
ZeroExt;
}
load_indexed! {
"lbzx" 3, 4, 5;
0x7c6428ae;
Width8Bit;
ZeroExt;
}
load_indexed! {
"lbzx" 3, 0, 5;
0x7c6028ae;
Width8Bit;
ZeroExt;
}
load_update! {
"lbzu" 3, 0x1234(4);
0x8c641234;
Width8Bit;
ZeroExt;
}
load_update_indexed! {
"lbzux" 3, 4, 5;
0x7c6428ee;
Width8Bit;
ZeroExt;
}
load! {
"lhz" 3, 0x1234(4);
0xa0641234;
Width16Bit;
ZeroExt;
}
load! {
"lhz" 3, 0x1234(0);
0xa0601234;
Width16Bit;
ZeroExt;
}
load_prefixed! {
"plhz" 3, 0x123456789(4), 0;
0x06012345, 0xa0646789;
Width16Bit;
ZeroExt;
}
load_prefixed! {
"plhz" 3, 0x123456789(0), 0;
0x06012345, 0xa0606789;
Width16Bit;
ZeroExt;
}
load_prefixed! {
"plhz" 3, 0x123456789(0), 1;
0x06112345, 0xa0606789;
Width16Bit;
ZeroExt;
}
load_indexed! {
"lhzx" 3, 4, 5;
0x7c642a2e;
Width16Bit;
ZeroExt;
}
load_indexed! {
"lhzx" 3, 0, 5;
0x7c602a2e;
Width16Bit;
ZeroExt;
}
load_update! {
"lhzu" 3, 0x1234(4);
0xa4641234;
Width16Bit;
ZeroExt;
}
load_update_indexed! {
"lhzux" 3, 4, 5;
0x7c642a6e;
Width16Bit;
ZeroExt;
}
load! {
"lha" 3, 0x1234(4);
0xa8641234;
Width16Bit;
SignExt;
}
load! {
"lha" 3, 0x1234(0);
0xa8601234;
Width16Bit;
SignExt;
}
load_prefixed! {
"plha" 3, 0x123456789(4), 0;
0x06012345, 0xa8646789;
Width16Bit;
SignExt;
}
load_prefixed! {
"plha" 3, 0x123456789(0), 0;
0x06012345, 0xa8606789;
Width16Bit;
SignExt;
}
load_prefixed! {
"plha" 3, 0x123456789(0), 1;
0x06112345, 0xa8606789;
Width16Bit;
SignExt;
}
load_indexed! {
"lhax" 3, 4, 5;
0x7c642aae;
Width16Bit;
SignExt;
}
load_indexed! {
"lhax" 3, 0, 5;
0x7c602aae;
Width16Bit;
SignExt;
}
load_update! {
"lhau" 3, 0x1234(4);
0xac641234;
Width16Bit;
SignExt;
}
load_update_indexed! {
"lhaux" 3, 4, 5;
0x7c642aee;
Width16Bit;
SignExt;
}
load! {
"lwz" 3, 0x1234(4);
0x80641234;
Width32Bit;
ZeroExt;
}
load! {
"lwz" 3, 0x1234(0);
0x80601234;
Width32Bit;
ZeroExt;
}
load_prefixed! {
"plwz" 3, 0x123456789(4), 0;
0x06012345, 0x80646789;
Width32Bit;
ZeroExt;
}
load_prefixed! {
"plwz" 3, 0x123456789(0), 0;
0x06012345, 0x80606789;
Width32Bit;
ZeroExt;
}
load_prefixed! {
"plwz" 3, 0x123456789(0), 1;
0x06112345, 0x80606789;
Width32Bit;
ZeroExt;
}
load_indexed! {
"lwzx" 3, 4, 5;
0x7c64282e;
Width32Bit;
ZeroExt;
}
load_indexed! {
"lwzx" 3, 0, 5;
0x7c60282e;
Width32Bit;
ZeroExt;
}
load_update! {
"lwzu" 3, 0x1234(4);
0x84641234;
Width32Bit;
ZeroExt;
}
load_update_indexed! {
"lwzux" 3, 4, 5;
0x7c64286e;
Width32Bit;
ZeroExt;
}
load! {
"lwa" 3, 0x1234(4);
0xe8641236;
Width32Bit;
SignExt;
}
load! {
"lwa" 3, 0x1234(0);
0xe8601236;
Width32Bit;
SignExt;
}
load_prefixed! {
"plwa" 3, 0x123456789(4), 0;
0x04012345, 0xa4646789;
Width32Bit;
SignExt;
}
load_prefixed! {
"plwa" 3, 0x123456789(0), 0;
0x04012345, 0xa4606789;
Width32Bit;
SignExt;
}
load_prefixed! {
"plwa" 3, 0x123456789(0), 1;
0x04112345, 0xa4606789;
Width32Bit;
SignExt;
}
load_indexed! {
"lwax" 3, 4, 5;
0x7c642aaa;
Width32Bit;
SignExt;
}
load_indexed! {
"lwax" 3, 0, 5;
0x7c602aaa;
Width32Bit;
SignExt;
}
// there is no `lwau`
load_update_indexed! {
"lwaux" 3, 4, 5;
0x7c642aea;
Width32Bit;
SignExt;
}
load! {
"ld" 3, 0x1234(4);
0xe8641234;
Width64Bit;
ZeroExt;
}
load! {
"ld" 3, 0x1234(0);
0xe8601234;
Width64Bit;
ZeroExt;
}
load_prefixed! {
"pld" 3, 0x123456789(4), 0;
0x04012345, 0xe4646789;
Width64Bit;
ZeroExt;
}
load_prefixed! {
"pld" 3, 0x123456789(0), 0;
0x04012345, 0xe4606789;
Width64Bit;
ZeroExt;
}
load_prefixed! {
"pld" 3, 0x123456789(0), 1;
0x04112345, 0xe4606789;
Width64Bit;
ZeroExt;
}
load_indexed! {
"ldx" 3, 4, 5;
0x7c64282a;
Width64Bit;
ZeroExt;
}
load_indexed! {
"ldx" 3, 0, 5;
0x7c60282a;
Width64Bit;
ZeroExt;
}
load_update! {
"ldu" 3, 0x1234(4);
0xe8641235;
Width64Bit;
ZeroExt;
}
load_update_indexed! {
"ldux" 3, 4, 5;
0x7c64286a;
Width64Bit;
ZeroExt;
}
}

View file

@ -1,273 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::test_cases::{TestCase, insn_empty, insn_single};
use cpu::instruction::{LogicalMOp, Lut4, MOpDestReg, MOpRegNum, MoveRegMOp, OutputIntegerMode};
use fayalite::prelude::*;
/// covers instructions in PowerISA v3.1C Book I 3.3.13 Fixed-Point Logical Instructions
pub fn test_cases_book_i_3_3_13_fixed_point_logical(retval: &mut Vec<TestCase>) {
macro_rules! insn_logic_i {
(
$mnemonic:literal $dest:literal, $src:literal, $imm:literal;
$encoding:literal;
|$a:ident, $b:ident| $lut_fn:expr;
) => {
retval.push(insn_single(
concat!(
$mnemonic,
" ",
stringify!($dest),
", ",
stringify!($src),
", ",
stringify!($imm)
),
$encoding,
None,
LogicalMOp::logical_i(
MOpDestReg::new_sim(
&[MOpRegNum::power_isa_gpr_reg_num($dest)],
if $mnemonic.contains('.') {
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM]
} else {
&[]
},
),
[MOpRegNum::power_isa_gpr_reg_imm($src)],
(($imm as u32) << if $mnemonic.contains('s') { 16 } else { 0 })
.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
Lut4::from_fn(|$a, $b| $lut_fn),
),
));
};
}
insn_logic_i! {
"andi." 3, 4, 0x89ab;
0x708389ab;
|a, b| a & b;
}
insn_logic_i! {
"andis." 3, 4, 0x89ab;
0x748389ab;
|a, b| a & b;
}
insn_logic_i! {
"ori" 3, 4, 0x89ab;
0x608389ab;
|a, b| a | b;
}
// ensure nop decodes to zero instructions
retval.push(insn_empty("ori 0, 0, 0", 0x60000000, None));
insn_logic_i! {
"oris" 3, 4, 0x89ab;
0x648389ab;
|a, b| a | b;
}
insn_logic_i! {
"xori" 3, 4, 0x89ab;
0x688389ab;
|a, b| a ^ b;
}
insn_logic_i! {
"xori" 0, 0, 0; // ensure xnop actually decodes to a normal ALU instruction
0x68000000;
|a, b| a ^ b;
}
insn_logic_i! {
"xoris" 3, 4, 0x89ab;
0x6c8389ab;
|a, b| a ^ b;
}
macro_rules! insn_logic {
(
$mnemonic:literal $dest:literal, $src0:literal, $src1:literal;
$encoding:literal;
|$a:ident, $b:ident| $lut_fn:expr;
) => {
retval.push(insn_single(
concat!(
$mnemonic,
" ",
stringify!($dest),
", ",
stringify!($src0),
", ",
stringify!($src1)
),
$encoding,
None,
LogicalMOp::logical(
MOpDestReg::new_sim(
&[MOpRegNum::power_isa_gpr_reg_num($dest)],
if $mnemonic.contains('.') {
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM]
} else {
&[]
},
),
[
MOpRegNum::power_isa_gpr_reg_imm($src0),
MOpRegNum::power_isa_gpr_reg_imm($src1),
],
0.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
Lut4::from_fn(|$a, $b| $lut_fn),
),
));
};
}
insn_logic! {
"and" 3, 4, 5;
0x7c832838;
|a, b| a & b;
}
insn_logic! {
"and." 3, 4, 5;
0x7c832839;
|a, b| a & b;
}
insn_logic! {
"xor" 3, 4, 5;
0x7c832a78;
|a, b| a ^ b;
}
insn_logic! {
"xor." 3, 4, 5;
0x7c832a79;
|a, b| a ^ b;
}
insn_logic! {
"nand" 3, 4, 5;
0x7c832bb8;
|a, b| !(a & b);
}
insn_logic! {
"nand." 3, 4, 5;
0x7c832bb9;
|a, b| !(a & b);
}
insn_logic! {
"or" 3, 4, 5;
0x7c832b78;
|a, b| a | b;
}
retval.push(insn_single(
"or 3, 4, 4", // mr 3, 4
0x7c832378,
None,
MoveRegMOp::move_reg(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(3)], &[]),
[MOpRegNum::power_isa_gpr_reg_imm(4)],
0.cast_to_static::<SInt<_>>(),
),
));
insn_logic! {
"or." 3, 4, 5;
0x7c832b79;
|a, b| a | b;
}
insn_logic! {
"or." 3, 4, 4; // mr. 3, 4
0x7c832379;
|a, b| a | b;
}
insn_logic! {
"orc" 3, 4, 5;
0x7c832b38;
|a, b| a | !b;
}
insn_logic! {
"orc." 3, 4, 5;
0x7c832b39;
|a, b| a | !b;
}
insn_logic! {
"nor" 3, 4, 5;
0x7c8328f8;
|a, b| !(a | b);
}
insn_logic! {
"nor." 3, 4, 5;
0x7c8328f9;
|a, b| !(a | b);
}
insn_logic! {
"eqv" 3, 4, 5;
0x7c832a38;
|a, b| a == b;
}
insn_logic! {
"eqv." 3, 4, 5;
0x7c832a39;
|a, b| a == b;
}
insn_logic! {
"andc" 3, 4, 5;
0x7c832878;
|a, b| a & !b;
}
insn_logic! {
"andc." 3, 4, 5;
0x7c832879;
|a, b| a & !b;
}
macro_rules! insn_exts {
(
$mnemonic:literal $dest:literal, $src:literal;
$encoding:literal;
$OutputIntegerMode:ident;
) => {
retval.push(insn_single(
concat!($mnemonic, " ", stringify!($dest), ", ", stringify!($src)),
$encoding,
None,
LogicalMOp::logical_i(
MOpDestReg::new_sim(
&[MOpRegNum::power_isa_gpr_reg_num($dest)],
if $mnemonic.contains('.') {
&[MOpRegNum::POWER_ISA_CR_0_REG_NUM]
} else {
&[]
},
),
[MOpRegNum::power_isa_gpr_reg_imm($src)],
0.cast_to_static::<SInt<_>>(),
OutputIntegerMode.$OutputIntegerMode(),
Lut4::from_fn(|a, b| a | b),
),
));
};
}
insn_exts! {
"extsb" 3, 4;
0x7c830774;
SignExt8;
}
insn_exts! {
"extsb." 3, 4;
0x7c830775;
SignExt8;
}
insn_exts! {
"extsh" 3, 4;
0x7c830734;
SignExt16;
}
insn_exts! {
"extsh." 3, 4;
0x7c830735;
SignExt16;
}
insn_exts! {
"extsw" 3, 4;
0x7c8307b4;
SignExt32;
}
insn_exts! {
"extsw." 3, 4;
0x7c8307b5;
SignExt32;
}
}

View file

@ -1,512 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::test_cases::{TestCase, insn_double, insn_triple};
use cpu::instruction::{
AddSubMOp, LoadStoreConversion, LoadStoreWidth, MOpDestReg, MOpRegNum, MoveRegMOp,
OutputIntegerMode, StoreMOp,
};
use fayalite::prelude::*;
/// covers instructions in PowerISA v3.1C Book I 3.3.3 Fixed-Point Store Instructions
pub fn test_cases_book_i_3_3_3_fixed_point_store(retval: &mut Vec<TestCase>) {
macro_rules! store_prefixed {
(
$mnemonic:literal $rs:literal, $disp:literal($ra:literal), $r:literal;
$prefix:literal, $suffix:literal;
$width:ident;
) => {
retval.push(insn_double(
concat!($mnemonic, " ", $rs, ", ", $disp, "(", $ra, "), ", $r),
$prefix,
Some($suffix),
[
AddSubMOp::add_sub_i(
MOpDestReg::new_sim(&[MOpRegNum::POWER_ISA_TEMP_REG_NUM], &[]),
[
if $r != 0 || $ra == 0 {
MOpRegNum::const_zero()
} else {
MOpRegNum::power_isa_gpr_reg_imm($ra)
},
MOpRegNum::const_zero(),
],
($disp as i64).cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
false,
false,
$r != 0,
),
StoreMOp::store(
MOpDestReg::new_sim(&[], &[]),
[
MOpRegNum::power_isa_temp_reg(),
MOpRegNum::power_isa_gpr_reg_imm($rs),
],
LoadStoreWidth.$width(),
LoadStoreConversion.ZeroExt(),
),
],
));
};
}
macro_rules! store {
(
$mnemonic:literal $rs:literal, $disp:literal($ra:literal);
$encoding:literal;
$width:ident;
) => {
retval.push(insn_double(
concat!($mnemonic, " ", $rs, ", ", $disp, "(", $ra, ")"),
$encoding,
None,
[
AddSubMOp::add_sub_i(
MOpDestReg::new_sim(&[MOpRegNum::POWER_ISA_TEMP_REG_NUM], &[]),
[
if $ra == 0 {
MOpRegNum::const_zero()
} else {
MOpRegNum::power_isa_gpr_reg_imm($ra)
},
MOpRegNum::const_zero(),
],
($disp as i64).cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
false,
false,
false,
),
StoreMOp::store(
MOpDestReg::new_sim(&[], &[]),
[
MOpRegNum::power_isa_temp_reg(),
MOpRegNum::power_isa_gpr_reg_imm($rs),
],
LoadStoreWidth.$width(),
LoadStoreConversion.ZeroExt(),
),
],
));
};
}
macro_rules! store_update {
(
$mnemonic:literal $rs:literal, $disp:literal($ra:literal);
$encoding:literal;
$width:ident;
) => {
if $ra == $rs {
retval.push(insn_triple(
concat!($mnemonic, " ", $rs, ", ", $disp, "(", $ra, ")"),
$encoding,
None,
[
AddSubMOp::add_sub_i(
MOpDestReg::new_sim(&[MOpRegNum::POWER_ISA_TEMP_REG_NUM], &[]),
[
MOpRegNum::power_isa_gpr_reg_imm($ra),
MOpRegNum::const_zero(),
],
($disp as i64).cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
false,
false,
false,
),
StoreMOp::store(
MOpDestReg::new_sim(&[], &[]),
[
MOpRegNum::power_isa_temp_reg(),
MOpRegNum::power_isa_gpr_reg_imm($rs),
],
LoadStoreWidth.$width(),
LoadStoreConversion.ZeroExt(),
),
MoveRegMOp::move_reg(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num($ra)], &[]),
[MOpRegNum::power_isa_temp_reg()],
0.cast_to_static::<SInt<_>>(),
),
],
));
} else {
retval.push(insn_double(
concat!($mnemonic, " ", $rs, ", ", $disp, "(", $ra, ")"),
$encoding,
None,
[
AddSubMOp::add_sub_i(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num($ra)], &[]),
[
MOpRegNum::power_isa_gpr_reg_imm($ra),
MOpRegNum::const_zero(),
],
($disp as i64).cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
false,
false,
false,
),
StoreMOp::store(
MOpDestReg::new_sim(&[], &[]),
[
MOpRegNum::power_isa_gpr_reg_imm($ra),
MOpRegNum::power_isa_gpr_reg_imm($rs),
],
LoadStoreWidth.$width(),
LoadStoreConversion.ZeroExt(),
),
],
));
}
};
}
macro_rules! store_indexed {
(
$mnemonic:literal $rs:literal, $ra:literal, $rb:literal;
$encoding:literal;
$width:ident;
) => {
retval.push(insn_double(
concat!($mnemonic, " ", $rs, ", ", $ra, ", ", $rb),
$encoding,
None,
[
AddSubMOp::add_sub_i(
MOpDestReg::new_sim(&[MOpRegNum::POWER_ISA_TEMP_REG_NUM], &[]),
[
if $ra == 0 {
MOpRegNum::const_zero()
} else {
MOpRegNum::power_isa_gpr_reg_imm($ra)
},
MOpRegNum::power_isa_gpr_reg_imm($rb),
],
0.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
false,
false,
false,
),
StoreMOp::store(
MOpDestReg::new_sim(&[], &[]),
[
MOpRegNum::power_isa_temp_reg(),
MOpRegNum::power_isa_gpr_reg_imm($rs),
],
LoadStoreWidth.$width(),
LoadStoreConversion.ZeroExt(),
),
],
));
};
}
macro_rules! store_update_indexed {
(
$mnemonic:literal $rs:literal, $ra:literal, $rb:literal;
$encoding:literal;
$width:ident;
) => {
if $ra == $rs {
retval.push(insn_triple(
concat!($mnemonic, " ", $rs, ", ", $ra, ", ", $rb),
$encoding,
None,
[
AddSubMOp::add_sub_i(
MOpDestReg::new_sim(&[MOpRegNum::POWER_ISA_TEMP_REG_NUM], &[]),
[
MOpRegNum::power_isa_gpr_reg_imm($ra),
MOpRegNum::power_isa_gpr_reg_imm($rb),
],
0.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
false,
false,
false,
),
StoreMOp::store(
MOpDestReg::new_sim(&[], &[]),
[
MOpRegNum::power_isa_temp_reg(),
MOpRegNum::power_isa_gpr_reg_imm($rs),
],
LoadStoreWidth.$width(),
LoadStoreConversion.ZeroExt(),
),
MoveRegMOp::move_reg(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num($ra)], &[]),
[MOpRegNum::power_isa_temp_reg()],
0.cast_to_static::<SInt<_>>(),
),
],
));
} else {
retval.push(insn_double(
concat!($mnemonic, " ", $rs, ", ", $ra, ", ", $rb),
$encoding,
None,
[
AddSubMOp::add_sub_i(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num($ra)], &[]),
[
if $ra == 0 {
MOpRegNum::const_zero()
} else {
MOpRegNum::power_isa_gpr_reg_imm($ra)
},
MOpRegNum::power_isa_gpr_reg_imm($rb),
],
0.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
false,
false,
false,
),
StoreMOp::store(
MOpDestReg::new_sim(&[], &[]),
[
MOpRegNum::power_isa_gpr_reg_imm($ra),
MOpRegNum::power_isa_gpr_reg_imm($rs),
],
LoadStoreWidth.$width(),
LoadStoreConversion.ZeroExt(),
),
],
));
}
};
}
store! {
"stb" 3, 0x1234(4);
0x98641234;
Width8Bit;
}
store! {
"stb" 3, 0x1234(0);
0x98601234;
Width8Bit;
}
store_prefixed! {
"pstb" 3, 0x123456789(4), 0;
0x06012345, 0x98646789;
Width8Bit;
}
store_prefixed! {
"pstb" 3, 0x123456789(0), 0;
0x06012345, 0x98606789;
Width8Bit;
}
store_prefixed! {
"pstb" 3, 0x123456789(0), 1;
0x06112345, 0x98606789;
Width8Bit;
}
store_indexed! {
"stbx" 3, 4, 5;
0x7c6429ae;
Width8Bit;
}
store_indexed! {
"stbx" 3, 0, 5;
0x7c6029ae;
Width8Bit;
}
store_update! {
"stbu" 3, 0x1234(4);
0x9c641234;
Width8Bit;
}
store_update! {
"stbu" 3, 0x1234(3);
0x9c631234;
Width8Bit;
}
store_update_indexed! {
"stbux" 3, 4, 5;
0x7c6429ee;
Width8Bit;
}
store_update_indexed! {
"stbux" 3, 3, 5;
0x7c6329ee;
Width8Bit;
}
store! {
"sth" 3, 0x1234(4);
0xb0641234;
Width16Bit;
}
store! {
"sth" 3, 0x1234(0);
0xb0601234;
Width16Bit;
}
store_prefixed! {
"psth" 3, 0x123456789(4), 0;
0x06012345, 0xb0646789;
Width16Bit;
}
store_prefixed! {
"psth" 3, 0x123456789(0), 0;
0x06012345, 0xb0606789;
Width16Bit;
}
store_prefixed! {
"psth" 3, 0x123456789(0), 1;
0x06112345, 0xb0606789;
Width16Bit;
}
store_indexed! {
"sthx" 3, 4, 5;
0x7c642b2e;
Width16Bit;
}
store_indexed! {
"sthx" 3, 0, 5;
0x7c602b2e;
Width16Bit;
}
store_update! {
"sthu" 3, 0x1234(4);
0xb4641234;
Width16Bit;
}
store_update! {
"sthu" 3, 0x1234(3);
0xb4631234;
Width16Bit;
}
store_update_indexed! {
"sthux" 3, 4, 5;
0x7c642b6e;
Width16Bit;
}
store_update_indexed! {
"sthux" 3, 3, 5;
0x7c632b6e;
Width16Bit;
}
store! {
"stw" 3, 0x1234(4);
0x90641234;
Width32Bit;
}
store! {
"stw" 3, 0x1234(0);
0x90601234;
Width32Bit;
}
store_prefixed! {
"pstw" 3, 0x123456789(4), 0;
0x06012345, 0x90646789;
Width32Bit;
}
store_prefixed! {
"pstw" 3, 0x123456789(0), 0;
0x06012345, 0x90606789;
Width32Bit;
}
store_prefixed! {
"pstw" 3, 0x123456789(0), 1;
0x06112345, 0x90606789;
Width32Bit;
}
store_indexed! {
"stwx" 3, 4, 5;
0x7c64292e;
Width32Bit;
}
store_indexed! {
"stwx" 3, 0, 5;
0x7c60292e;
Width32Bit;
}
store_update! {
"stwu" 3, 0x1234(4);
0x94641234;
Width32Bit;
}
store_update! {
"stwu" 3, 0x1234(3);
0x94631234;
Width32Bit;
}
store_update_indexed! {
"stwux" 3, 4, 5;
0x7c64296e;
Width32Bit;
}
store_update_indexed! {
"stwux" 3, 3, 5;
0x7c63296e;
Width32Bit;
}
store! {
"std" 3, 0x1234(4);
0xf8641234;
Width64Bit;
}
store! {
"std" 3, 0x1234(0);
0xf8601234;
Width64Bit;
}
store_prefixed! {
"pstd" 3, 0x123456789(4), 0;
0x04012345, 0xf4646789;
Width64Bit;
}
store_prefixed! {
"pstd" 3, 0x123456789(0), 0;
0x04012345, 0xf4606789;
Width64Bit;
}
store_prefixed! {
"pstd" 3, 0x123456789(0), 1;
0x04112345, 0xf4606789;
Width64Bit;
}
store_indexed! {
"stdx" 3, 4, 5;
0x7c64292a;
Width64Bit;
}
store_indexed! {
"stdx" 3, 0, 5;
0x7c60292a;
Width64Bit;
}
store_update! {
"stdu" 3, 0x1234(4);
0xf8641235;
Width64Bit;
}
store_update! {
"stdu" 3, 0x1234(3);
0xf8631235;
Width64Bit;
}
store_update_indexed! {
"stdux" 3, 4, 5;
0x7c64296a;
Width64Bit;
}
store_update_indexed! {
"stdux" 3, 3, 5;
0x7c63296a;
Width64Bit;
}
}

View file

@ -1,149 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::test_cases::{TestCase, insn_single};
use cpu::instruction::{
LogicalFlagsMOp, LogicalFlagsMOpImm, Lut4, MOpDestReg, MOpRegNum, MoveRegMOp, ReadSpecialMOp,
ReadSpecialMOpImm,
};
use fayalite::prelude::*;
/// covers instructions in PowerISA v3.1C Book I 3.3.19 Move To/From System Register Instructions
pub fn test_cases_book_i_3_3_19_move_to_from_system_register(retval: &mut Vec<TestCase>) {
// mfspr/mtspr are covered by test_cases_book_iii_5_4_4_move_to_from_system_register
#[hdl]
fn mcrxrx_imm() -> SimValue<LogicalFlagsMOpImm> {
#[hdl(sim)]
LogicalFlagsMOpImm {
// if the order of flags in PRegFlags changes, this will need to be updated
src0_start: 4usize.cast_to(LogicalFlagsMOpImm.src0_start),
src1_start: 4usize.cast_to(LogicalFlagsMOpImm.src1_start),
src2_start: 4usize.cast_to(LogicalFlagsMOpImm.src2_start),
dest_start: 0usize.cast_to(LogicalFlagsMOpImm.dest_start),
dest_count: 6usize.cast_to(LogicalFlagsMOpImm.dest_count),
}
}
retval.push(insn_single(
"mcrxrx 3",
0x7d800480,
None,
LogicalFlagsMOp::logical_flags(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_cr_reg_num(3)], &[]),
[
MOpRegNum::power_isa_xer_ca_ca32_reg(),
MOpRegNum::const_zero(),
MOpRegNum::power_isa_xer_so_ov_ov32_reg(),
],
mcrxrx_imm(),
Lut4::from_fn(|a, b| a | b),
),
));
}
/// covers instructions in PowerISA v3.1C Book III 5.4.4 Move To/From System Register Instructions
pub fn test_cases_book_iii_5_4_4_move_to_from_system_register(retval: &mut Vec<TestCase>) {
retval.push(insn_single(
"mflr 3",
0x7c6802a6,
None,
MoveRegMOp::move_reg(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(3)], &[]),
[MOpRegNum::power_isa_lr_reg()],
0.cast_to_static::<SInt<_>>(),
),
));
retval.push(insn_single(
"mtlr 3",
0x7c6803a6,
None,
MoveRegMOp::move_reg(
MOpDestReg::new_sim(&[MOpRegNum::POWER_ISA_LR_REG_NUM], &[]),
[MOpRegNum::power_isa_gpr_reg_imm(3)],
0.cast_to_static::<SInt<_>>(),
),
));
retval.push(insn_single(
"mfctr 3",
0x7c6902a6,
None,
MoveRegMOp::move_reg(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(3)], &[]),
[MOpRegNum::power_isa_ctr_reg()],
0.cast_to_static::<SInt<_>>(),
),
));
retval.push(insn_single(
"mtctr 3",
0x7c6903a6,
None,
MoveRegMOp::move_reg(
MOpDestReg::new_sim(&[MOpRegNum::POWER_ISA_CTR_REG_NUM], &[]),
[MOpRegNum::power_isa_gpr_reg_imm(3)],
0.cast_to_static::<SInt<_>>(),
),
));
retval.push(insn_single(
"mfspr 3, 815 # mftar 3",
0x7c6fcaa6,
None,
MoveRegMOp::move_reg(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(3)], &[]),
[MOpRegNum::power_isa_tar_reg()],
0.cast_to_static::<SInt<_>>(),
),
));
retval.push(insn_single(
"mtspr 815, 3 # mttar 3",
0x7c6fcba6,
None,
MoveRegMOp::move_reg(
MOpDestReg::new_sim(&[MOpRegNum::POWER_ISA_TAR_REG_NUM], &[]),
[MOpRegNum::power_isa_gpr_reg_imm(3)],
0.cast_to_static::<SInt<_>>(),
),
));
// make sure we generate mfspr and not the phased-out mftb
retval.push(insn_single(
"mfspr 3, 268 # mftb 3",
0x7c6c42a6,
None,
ReadSpecialMOp::read_special(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(3)], &[]),
[MOpRegNum::const_zero(); 0],
ReadSpecialMOpImm.PowerIsaTimeBase(),
),
));
// make sure we generate mfspr and not the phased-out mftb
retval.push(insn_single(
"mfspr 3, 269 # mftbu 3",
0x7c6d42a6,
None,
ReadSpecialMOp::read_special(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(3)], &[]),
[MOpRegNum::const_zero(); 0],
ReadSpecialMOpImm.PowerIsaTimeBaseU(),
),
));
// phased-out mftb -- not actually generated by the assembler so we have to use .long
retval.push(insn_single(
".long 0x7c6c42e6 # mftb 3, 268",
0x7c6c42e6,
None,
ReadSpecialMOp::read_special(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(3)], &[]),
[MOpRegNum::const_zero(); 0],
ReadSpecialMOpImm.PowerIsaTimeBase(),
),
));
// phased-out mftb -- not actually generated by the assembler so we have to use .long
retval.push(insn_single(
".long 0x7c6d42e6 # mftb 3, 269",
0x7c6d42e6,
None,
ReadSpecialMOp::read_special(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(3)], &[]),
[MOpRegNum::const_zero(); 0],
ReadSpecialMOpImm.PowerIsaTimeBaseU(),
),
));
}

View file

@ -1,16 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::test_cases::{TestCase, insn_empty};
/// covers instructions in PowerISA v3.1C Book I 3.3.20 Prefixed No-Operation Instruction
pub fn test_cases_book_i_3_3_20_prefixed_no_operation(retval: &mut Vec<TestCase>) {
// ensure pnop decodes to zero instructions
retval.push(insn_empty(
// LLVM doesn't support the pnop instruction:
// https://github.com/llvm/llvm-project/issues/176831
".long 0x07000000, 0 # pnop",
0x07000000,
Some(0),
));
}

View file

@ -1,980 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use cpu::{
main_memory_and_io::{
MemoryInterface, MemoryInterfaceConfig, MemoryOperationErrorKind, MemoryOperationFinish,
MemoryOperationFinishKind, MemoryOperationKind, MemoryOperationStart,
simple_uart::{
ReceiverQueueStatus, SIMPLE_UART_RECEIVE_OFFSET, SIMPLE_UART_STATUS_OFFSET,
SIMPLE_UART_TRANSMIT_OFFSET, SIMPLE_UART_USED_SIZE, receiver, receiver_no_queue,
simple_uart, simple_uart_memory_interface_config, transmitter, uart_clock_gen,
},
},
next_pc::FETCH_BLOCK_ID_WIDTH,
util::array_vec::ArrayVec,
};
use fayalite::{
bundle::BundleType,
intern::{Intern, Interned},
prelude::*,
sim::vcd::VcdWriterDecls,
util::{RcWriter, ready_valid::ReadyValid},
};
use std::{
borrow::BorrowMut,
marker::PhantomData,
num::{NonZeroUsize, Wrapping},
};
const fn half_period(frequency: f64) -> SimDuration {
SimDuration::from_attos((SimDuration::from_secs(1).as_attos() as f64 / frequency / 2.0) as u128)
}
const CLOCK_FREQUENCY: f64 = 200_000.0;
const CLOCK_HALF_PERIOD: SimDuration = half_period(CLOCK_FREQUENCY);
const BAUD_RATE: f64 = 9600.0;
const BAUD_HALF_PERIOD: SimDuration = half_period(BAUD_RATE);
const BAUD_PERIOD: SimDuration = BAUD_HALF_PERIOD.saturating_add(BAUD_HALF_PERIOD);
const fn baud_period_slow_percentage(percent: i128) -> SimDuration {
let v = BAUD_PERIOD.as_attos();
let v = v.strict_add_signed(v as i128 * percent / 100);
SimDuration::from_attos(v)
}
/// 3% slow BAUD_PERIOD
const BAUD_PERIOD_SLOW: SimDuration = baud_period_slow_percentage(3);
/// 3% fast BAUD_PERIOD
const BAUD_PERIOD_FAST: SimDuration = baud_period_slow_percentage(-3);
const _: () = {
assert!(BAUD_HALF_PERIOD.as_attos() < BAUD_PERIOD_FAST.as_attos());
assert!(BAUD_PERIOD_FAST.as_attos() < BAUD_PERIOD.as_attos());
assert!(BAUD_PERIOD.as_attos() < BAUD_PERIOD_SLOW.as_attos());
};
fn clock_input_properties() -> PhantomConst<peripherals::ClockInputProperties> {
peripherals::ClockInput::new(CLOCK_FREQUENCY).properties
}
#[hdl_module(extern)]
fn reference_baud_rate_clk_gen() {
#[hdl]
let reference_baud_rate_clk: Clock = m.output();
m.extern_module_simulation_fn(
reference_baud_rate_clk,
async |reference_baud_rate_clk, mut sim| {
loop {
sim.write_clock(reference_baud_rate_clk, false).await;
sim.advance_time(BAUD_HALF_PERIOD).await;
sim.write_clock(reference_baud_rate_clk, true).await;
sim.advance_time(BAUD_HALF_PERIOD).await;
}
},
);
}
#[hdl_module(extern)]
fn sim_receiver() {
#[hdl]
let tx: Bool = m.input();
#[hdl]
let text_out: SimOnly<String> = m.output();
m.extern_module_simulation_fn((tx, text_out), async |(tx, text_out), mut sim| {
let mut text = SimOnlyValue::new(String::new());
sim.write(text_out, &text).await;
loop {
// wait for the starting edge of the start bit
while sim.read_bool(tx).await {
sim.wait_for_changes([tx], None).await;
}
// wait till the middle of the start bit
sim.advance_time(BAUD_HALF_PERIOD).await;
let mut byte = 0u8;
for i in 0..u8::BITS {
// wait till the middle of the next data bit
sim.advance_time(BAUD_PERIOD).await;
if sim.read_bool(tx).await {
byte |= 1 << i;
}
}
assert!(byte.is_ascii());
text.push(byte as char);
sim.write(text_out, &text).await;
// wait till the middle of the stop bit
sim.advance_time(BAUD_PERIOD).await;
// we're in the middle of the stop bit, the stop bit is a one so we can just loop and wait for
// the next transition to zero which is the next start bit.
}
});
}
#[hdl_module]
fn transmitter_and_uart_clock_gen() {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let tx: Bool = m.output();
#[hdl]
let input_byte: ReadyValid<UInt<8>> = m.input();
#[hdl]
let reference_baud_rate_clk: Clock = m.output();
#[hdl]
let text_out: SimOnly<String> = m.output();
#[hdl]
let sim_receiver = instance(sim_receiver());
connect(sim_receiver.tx, tx);
connect(text_out, sim_receiver.text_out);
#[hdl]
let reference_baud_rate_clk_gen = instance(reference_baud_rate_clk_gen());
connect(
reference_baud_rate_clk,
reference_baud_rate_clk_gen.reference_baud_rate_clk,
);
#[hdl]
let transmitter = instance(transmitter());
connect(transmitter.cd, cd);
connect(transmitter.input_byte, input_byte);
connect(tx, transmitter.tx);
#[hdl]
let clock_gen = instance(uart_clock_gen(clock_input_properties(), BAUD_RATE));
connect(clock_gen.cd, cd);
connect(transmitter.tick, clock_gen.tick);
}
#[test]
#[hdl]
fn test_transmitter_and_uart_clock_gen() {
let _n = SourceLocation::normalize_files_for_tests();
let m = transmitter_and_uart_clock_gen();
let mut sim = Simulation::new(m);
let writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
struct DumpVcdOnDrop {
writer: Option<RcWriter>,
}
impl Drop for DumpVcdOnDrop {
fn drop(&mut self) {
if let Some(mut writer) = self.writer.take() {
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
}
}
}
let mut writer = DumpVcdOnDrop {
writer: Some(writer),
};
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, true);
let text = "Hi!\n";
let mut bytes = text.as_bytes().iter();
for cycle in 0..1000 {
let input_byte = match bytes.clone().next() {
Some(b) if cycle > 10 =>
{
#[hdl(sim)]
HdlSome(b)
}
_ =>
{
#[hdl(sim)]
HdlNone()
}
};
sim.write(sim.io().input_byte.data, &input_byte);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
if sim.read_bool(sim.io().input_byte.ready) {
#[hdl(sim)]
if let HdlSome(_) = input_byte {
bytes.next();
}
}
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, false);
}
assert_eq!(sim.read(sim.io().text_out).as_str(), text);
assert!(bytes.as_slice().is_empty());
let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("expected/transmitter_and_uart_clock_gen.vcd") {
panic!();
}
}
#[hdl_module(extern)]
fn receiver_no_queue_test_cases(text: Interned<str>) {
#[hdl]
let rx: Bool = m.output();
#[hdl]
let cur_byte: UInt<8> = m.output();
m.extern_module_simulation_fn(
(rx, cur_byte, text),
async |(rx, cur_byte, text), mut sim| {
// ensure the receiver can properly handle slightly fast or slow baud rates
let baud_periods = [
BAUD_PERIOD_FAST,
BAUD_PERIOD_FAST,
BAUD_PERIOD_FAST,
BAUD_PERIOD_FAST,
BAUD_PERIOD,
BAUD_PERIOD,
BAUD_PERIOD,
BAUD_PERIOD,
BAUD_PERIOD_SLOW,
BAUD_PERIOD_SLOW,
BAUD_PERIOD_SLOW,
BAUD_PERIOD_SLOW,
]
.iter()
.copied()
.cycle();
sim.write(rx, true).await;
sim.write(cur_byte, 0u8).await;
// allow time for reset and stuff
sim.advance_time(SimDuration::from_micros(100)).await;
for (byte, baud_period) in text.bytes().cycle().zip(baud_periods) {
sim.write(cur_byte, byte).await;
sim.write(rx, false).await; // start bit
sim.advance_time(baud_period).await;
for i in 0..u8::BITS {
sim.write(rx, byte & (1 << i) != 0).await; // data bit
sim.advance_time(baud_period).await;
}
sim.write(rx, true).await; // stop bit
sim.advance_time(baud_period).await;
}
},
);
}
#[hdl_module]
fn receiver_no_queue_and_uart_clock_gen(text: Interned<str>) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let rx: Bool = m.output();
#[hdl]
let output_byte: HdlOption<UInt<8>> = m.output();
#[hdl]
let cur_byte: UInt<8> = m.output();
#[hdl]
let reference_baud_rate_clk: Clock = m.output();
#[hdl]
let test_cases = instance(receiver_no_queue_test_cases(text));
connect(rx, test_cases.rx);
connect(cur_byte, test_cases.cur_byte);
#[hdl]
let reference_baud_rate_clk_gen = instance(reference_baud_rate_clk_gen());
connect(
reference_baud_rate_clk,
reference_baud_rate_clk_gen.reference_baud_rate_clk,
);
// only need one stage of synchronization in the simulator
#[hdl]
let rx_synchronized = reg_builder().clock_domain(cd).reset(true);
connect(rx_synchronized, rx);
#[hdl]
let receiver = instance(receiver_no_queue());
connect(receiver.cd, cd);
connect(receiver.rx_synchronized, rx_synchronized);
connect(output_byte, receiver.output_byte);
#[hdl]
let clock_gen = instance(uart_clock_gen(clock_input_properties(), BAUD_RATE));
connect(clock_gen.cd, cd);
connect(receiver.tick, clock_gen.tick);
}
#[test]
#[hdl]
fn test_receiver_no_queue_and_uart_clock_gen() {
let _n = SourceLocation::normalize_files_for_tests();
let text = "Testing 123, testing, testing.\n".intern();
let m = receiver_no_queue_and_uart_clock_gen(text);
let mut sim = Simulation::new(m);
let writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
struct DumpVcdOnDrop {
writer: Option<RcWriter>,
}
impl Drop for DumpVcdOnDrop {
fn drop(&mut self) {
if let Some(mut writer) = self.writer.take() {
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
}
}
}
let mut writer = DumpVcdOnDrop {
writer: Some(writer),
};
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, true);
let mut received_text = String::new();
for _cycle in 0..10000 {
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, false);
#[hdl(sim)]
if let HdlSome(byte) = sim.read(sim.io().output_byte) {
let byte = byte.as_int();
let expected_byte = sim.read(sim.io().cur_byte).as_int();
assert_eq!(
byte, expected_byte,
"byte={:#x} {:?}, expected_byte={:#x} {:?}",
byte, byte as char, expected_byte, expected_byte as char,
);
assert!(byte.is_ascii());
received_text.push(byte as char);
}
}
println!("{received_text:?}");
assert!(received_text.len() >= text.len());
let mut expected_text = text.repeat(received_text.len().div_ceil(text.len()));
expected_text.truncate(received_text.len());
assert_eq!(received_text, expected_text);
let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("expected/receiver_no_queue_and_uart_clock_gen.vcd") {
panic!();
}
}
#[hdl_module(extern)]
fn receiver_test_cases() {
#[hdl]
let clk: Clock = m.input();
#[hdl]
let rx: Bool = m.output();
#[hdl]
let cur_byte: UInt<8> = m.output();
#[hdl]
let ready: Bool = m.output();
#[hdl]
let text: SimOnly<String> = m.input();
m.register_clock_for_past(clk);
m.extern_module_simulation_fn(
(clk, rx, cur_byte, ready, text),
async |(clk, rx, cur_byte, ready, text), mut sim| {
sim.write(rx, true).await;
sim.write(ready, false).await;
loop {
sim.write(cur_byte, 0u8).await;
sim.wait_for_clock_edge(clk).await;
sim.write(ready, true).await;
let text = loop {
sim.wait_for_clock_edge(clk).await;
let text = sim.read_past(text, clk).await;
if text.is_empty() {
continue;
}
break text;
};
sim.write(ready, false).await;
sim.wait_for_clock_edge(clk).await;
for byte in text.bytes() {
sim.write(cur_byte, byte).await;
sim.write(rx, false).await; // start bit
sim.advance_time(BAUD_PERIOD).await;
for i in 0..u8::BITS {
sim.write(rx, byte & (1 << i) != 0).await; // data bit
sim.advance_time(BAUD_PERIOD).await;
}
sim.write(rx, true).await; // stop bit
sim.advance_time(BAUD_PERIOD).await;
}
}
},
);
}
#[hdl_module]
fn receiver_and_uart_clock_gen(queue_capacity: NonZeroUsize) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let rx: Bool = m.output();
#[hdl]
let reference_baud_rate_clk: Clock = m.output();
#[hdl]
let cur_byte: UInt<8> = m.output();
#[hdl]
let ready: Bool = m.output();
#[hdl]
let text: SimOnly<String> = m.input();
#[hdl]
let output_byte: ReadyValid<UInt<8>> = m.output();
#[hdl]
let queue_status: ReceiverQueueStatus = m.output();
#[hdl]
let test_cases = instance(receiver_test_cases());
connect(test_cases.clk, cd.clk);
connect(rx, test_cases.rx);
connect(cur_byte, test_cases.cur_byte);
connect(ready, test_cases.ready);
connect(test_cases.text, text);
#[hdl]
let reference_baud_rate_clk_gen = instance(reference_baud_rate_clk_gen());
connect(
reference_baud_rate_clk,
reference_baud_rate_clk_gen.reference_baud_rate_clk,
);
// only need one stage of synchronization in the simulator
#[hdl]
let rx_synchronized = reg_builder().clock_domain(cd).reset(true);
connect(rx_synchronized, rx);
#[hdl]
let receiver = instance(receiver(queue_capacity));
connect(receiver.cd, cd);
connect(receiver.rx_synchronized, rx_synchronized);
connect(output_byte, receiver.output_byte);
connect(queue_status, receiver.queue_status);
#[hdl]
let clock_gen = instance(uart_clock_gen(clock_input_properties(), BAUD_RATE));
connect(clock_gen.cd, cd);
connect(receiver.tick, clock_gen.tick);
}
#[test]
#[hdl]
fn test_receiver_and_uart_clock_gen() {
let _n = SourceLocation::normalize_files_for_tests();
const QUEUE_SIZE: NonZeroUsize = NonZeroUsize::new(4).expect("not zero");
let m = receiver_and_uart_clock_gen(QUEUE_SIZE);
let mut sim = Simulation::new(m);
let writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
struct DumpVcdOnDrop {
writer: Option<RcWriter>,
}
impl Drop for DumpVcdOnDrop {
fn drop(&mut self) {
if let Some(mut writer) = self.writer.take() {
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
}
}
}
let mut writer = DumpVcdOnDrop {
writer: Some(writer),
};
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, true);
let empty_text = SimOnlyValue::new(String::new());
sim.write(sim.io().text, &empty_text);
sim.write_bool(sim.io().output_byte.ready, false);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, false);
const TEST_TEXT: &str = "Test\n";
const { assert!(TEST_TEXT.len() > QUEUE_SIZE.get()) }
for bytes_before_dequeue in 0..=TEST_TEXT.len() {
println!("\nbytes_before_dequeue={bytes_before_dequeue}");
for _cycle in 0..10 {
if sim.read_bool(sim.io().ready) {
break;
}
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, false);
}
assert!(sim.read_bool(sim.io().ready));
let text = SimOnlyValue::new(TEST_TEXT[..bytes_before_dequeue].to_string());
dbg!(&text);
sim.write(sim.io().text, &text);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, false);
sim.write(sim.io().text, &empty_text);
for _cycle in 0..2000 {
if sim.read_bool(sim.io().ready) {
break;
}
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, false);
}
assert!(sim.read_bool(sim.io().ready));
for _cycle in 0..10 {
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, false);
}
dbg!(bytes_before_dequeue);
#[hdl(sim)]
match dbg!(sim.read(sim.io().queue_status)) {
ReceiverQueueStatus::QueueEmpty => {
assert_eq!(bytes_before_dequeue, 0);
}
ReceiverQueueStatus::QueueNotEmpty => {
assert!(bytes_before_dequeue > 0 && bytes_before_dequeue < QUEUE_SIZE.get() - 1);
}
ReceiverQueueStatus::QueueAlmostFull => {
assert_eq!(bytes_before_dequeue, QUEUE_SIZE.get() - 1);
}
ReceiverQueueStatus::QueueFull => {
assert_eq!(bytes_before_dequeue, QUEUE_SIZE.get());
}
ReceiverQueueStatus::QueueOverflowed => {
assert!(bytes_before_dequeue > QUEUE_SIZE.get());
}
ReceiverQueueStatus::Unknown => unreachable!(),
}
let expected_text = &TEST_TEXT[..bytes_before_dequeue.min(QUEUE_SIZE.get())];
dbg!(expected_text);
let mut received_text = String::new();
for expected_byte in expected_text.bytes() {
sim.write_bool(sim.io().output_byte.ready, true);
#[hdl(sim)]
if let HdlSome(byte) = sim.read(sim.io().output_byte.data) {
let byte = byte.as_int();
assert_eq!(
byte, expected_byte,
"byte={:#x} {:?}, expected_byte={:#x} {:?}",
byte, byte as char, expected_byte, expected_byte as char,
);
assert!(byte.is_ascii());
received_text.push(byte as char);
} else {
break;
}
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, false);
}
#[hdl(sim)]
if let HdlSome(_) = sim.read(sim.io().output_byte.data) {
panic!("queue should be empty now");
}
sim.write_bool(sim.io().output_byte.ready, false);
}
let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("expected/receiver_and_uart_clock_gen.vcd") {
panic!();
}
}
#[hdl_module]
fn simple_uart_loopback(
config: PhantomConst<MemoryInterfaceConfig>,
clock_input_properties: PhantomConst<peripherals::ClockInputProperties>,
baud_rate: f64,
receiver_queue_size: NonZeroUsize,
) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let memory_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
m.input(MemoryInterface[config]);
#[hdl]
let tx: Bool = m.output();
#[hdl]
let inner = instance(simple_uart(
config,
clock_input_properties,
baud_rate,
receiver_queue_size,
));
connect(inner.cd, cd);
connect(inner.memory_interface, memory_interface);
connect(tx, inner.uart.tx);
connect(inner.uart.rx, inner.uart.tx);
}
const SIMPLE_UART_MEMORY_INTERFACE_CONFIG: MemoryInterfaceConfig =
simple_uart_memory_interface_config(FETCH_BLOCK_ID_WIDTH, Wrapping(0));
struct MemoryOperationRunner<Sim: BorrowMut<Simulation<T>>, T: BundleType> {
sim: Sim,
clk: Expr<Clock>,
memory_interface: Expr<MemoryInterface<PhantomConst<MemoryInterfaceConfig>>>,
next_op_id: u64,
_phantom: PhantomData<T>,
}
const BUS_WIDTH_IN_BYTES: usize = SIMPLE_UART_MEMORY_INTERFACE_CONFIG.bus_width_in_bytes();
impl<Sim: BorrowMut<Simulation<T>>, T: BundleType> MemoryOperationRunner<Sim, T> {
#[hdl]
fn run_memory_operation(
&mut self,
kind: impl ToSimValue<Type = MemoryOperationKind>,
addr: u64,
write_data: [u8; BUS_WIDTH_IN_BYTES],
rw_mask: [bool; BUS_WIDTH_IN_BYTES],
timeout_cycles: usize,
) -> Result<[u8; BUS_WIDTH_IN_BYTES], SimValue<MemoryOperationErrorKind>> {
let kind = kind.into_sim_value();
assert_eq!(addr % BUS_WIDTH_IN_BYTES as u64, 0);
let config = self.memory_interface.ty().config;
let op_id = self.next_op_id.cast_to(UInt[FETCH_BLOCK_ID_WIDTH]);
self.next_op_id += 1;
println!(
"started: MemoryOperationStart {{\n \
kind: {kind:?},\n \
addr: {addr:#x},\n \
write_data: {write_data:?},\n \
rw_mask: {rw_mask:?},\n \
op_id: {op_id},\n\
}}"
);
let start_value = #[hdl(sim)]
MemoryOperationStart::<_> {
kind,
addr,
write_data: &write_data[..],
rw_mask: &rw_mask[..],
op_id: op_id,
config,
};
#[hdl]
let MemoryInterface::<_> {
start,
finish,
next_op_ids,
config: _,
} = self.memory_interface;
let sim = self.sim.borrow_mut();
let check_io = |sim: &mut Simulation<T>,
expected_start_ready: bool,
expected_finish_data_is_some: bool,
expected_next_op_ids: &[_]| {
assert_eq!(sim.read_bool(start.ready), expected_start_ready);
let finish_data = sim.read(finish.data);
let finish_data_is_some = #[hdl(sim)]
if let HdlSome(_) = &finish_data {
true
} else {
false
};
assert_eq!(
finish_data_is_some, expected_finish_data_is_some,
"finish.data: {finish_data:?}"
);
#[hdl(sim)]
if let HdlSome(next_op_ids) = sim.read(next_op_ids) {
let next_op_ids: Vec<_> = ArrayVec::elements_sim_ref(&next_op_ids)
.iter()
.map(|v| {
v.cast_to_static::<UInt<{ FETCH_BLOCK_ID_WIDTH }>>()
.as_int()
})
.collect();
assert_eq!(next_op_ids, expected_next_op_ids);
} else {
panic!("next_op_ids should be HdlSome");
}
};
check_io(sim, true, false, &[]);
sim.write(
start.data,
#[hdl(sim)]
(start.ty().data).HdlSome(&start_value),
);
sim.write(finish.ready, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(self.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(self.clk, false);
sim.write(
start.data,
#[hdl(sim)]
(start.ty().data).HdlNone(),
);
check_io(
sim,
false,
false,
&[start_value
.op_id
.cast_to_static::<UInt<{ FETCH_BLOCK_ID_WIDTH }>>()
.as_int()],
);
for _ in 0..timeout_cycles {
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(self.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(self.clk, false);
#[hdl(sim)]
if let HdlSome(finish_data) = sim.read(finish.data) {
#[hdl(sim)]
let MemoryOperationFinish::<_> {
kind,
read_data,
config: _,
} = finish_data;
assert_eq!(read_data.len(), BUS_WIDTH_IN_BYTES);
let read_data = std::array::from_fn(|i| read_data[i].as_int());
println!(
"finished: MemoryOperationFinish {{\n \
kind: {kind:?},\n \
read_data: {read_data:?},\n\
}}"
);
check_io(
sim,
false,
true,
&[start_value
.op_id
.cast_to_static::<UInt<{ FETCH_BLOCK_ID_WIDTH }>>()
.as_int()],
);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(self.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(self.clk, false);
sim.write(finish.ready, false);
check_io(sim, true, false, &[]);
return #[hdl(sim)]
match kind {
MemoryOperationFinishKind::Success(kind) => {
assert_eq!(
SimValue::bits(&kind),
SimValue::bits(&start_value.kind),
"finish_data.kind={kind:?}",
);
Ok(read_data)
}
MemoryOperationFinishKind::Error(error) => Err(error),
};
}
check_io(
sim,
false,
false,
&[start_value
.op_id
.cast_to_static::<UInt<{ FETCH_BLOCK_ID_WIDTH }>>()
.as_int()],
);
}
panic!("memory operation took too long");
}
#[hdl]
fn rw_bytes(
&mut self,
start_addr: u64,
is_read: bool,
bytes: &mut [u8],
timeout_cycles: usize,
) -> Result<(), SimValue<MemoryOperationErrorKind>> {
let offset_in_chunk = start_addr as usize % BUS_WIDTH_IN_BYTES;
let masked_addr = start_addr - offset_in_chunk as u64;
assert!(
offset_in_chunk
.checked_add(bytes.len())
.is_some_and(|v| v <= BUS_WIDTH_IN_BYTES)
);
let bytes_range = offset_in_chunk..(offset_in_chunk + bytes.len());
let mut write_data = [0u8; BUS_WIDTH_IN_BYTES];
let mut rw_mask = [false; BUS_WIDTH_IN_BYTES];
rw_mask[bytes_range.clone()].fill(true);
if !is_read {
write_data[bytes_range.clone()].copy_from_slice(bytes);
}
let read_bytes = self.run_memory_operation(
if is_read {
#[hdl(sim)]
MemoryOperationKind.Read()
} else {
#[hdl(sim)]
MemoryOperationKind.Write()
},
masked_addr,
write_data,
rw_mask,
timeout_cycles,
)?;
if is_read {
bytes.copy_from_slice(&read_bytes[bytes_range]);
}
Ok(())
}
#[hdl]
fn read_bytes<const N: usize>(
&mut self,
start_addr: u64,
timeout_cycles: usize,
) -> Result<[u8; N], SimValue<MemoryOperationErrorKind>> {
let mut retval = [0; _];
self.rw_bytes(start_addr, true, &mut retval, timeout_cycles)?;
Ok(retval)
}
#[hdl]
fn write_bytes<const N: usize>(
&mut self,
start_addr: u64,
mut bytes: [u8; N],
timeout_cycles: usize,
) -> Result<(), SimValue<MemoryOperationErrorKind>> {
self.rw_bytes(start_addr, false, &mut bytes, timeout_cycles)
}
}
#[test]
#[hdl]
fn test_simple_uart() {
let _n = SourceLocation::normalize_files_for_tests();
const RECEIVER_QUEUE_SIZE: NonZeroUsize = NonZeroUsize::new(16).expect("not zero");
let m = simple_uart_loopback(
PhantomConst::new_sized(SIMPLE_UART_MEMORY_INTERFACE_CONFIG),
clock_input_properties(),
BAUD_RATE,
RECEIVER_QUEUE_SIZE,
);
let mut sim = Simulation::new(m);
let writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
struct DumpVcdOnDrop {
writer: Option<RcWriter>,
}
impl Drop for DumpVcdOnDrop {
fn drop(&mut self) {
if let Some(mut writer) = self.writer.take() {
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
}
}
}
let mut writer = DumpVcdOnDrop {
writer: Some(writer),
};
sim.write(
sim.io().memory_interface.start.data,
sim.io().ty().memory_interface.start.data.HdlNone(),
);
sim.write(sim.io().memory_interface.finish.ready, false);
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, false);
let mut mem_op_runner = MemoryOperationRunner {
clk: sim.io().cd.clk,
memory_interface: sim.io().memory_interface,
next_op_id: 0,
_phantom: PhantomData,
sim,
};
let empty_status = ReceiverQueueStatus::sim_as_u8(
#[hdl(sim)]
ReceiverQueueStatus.QueueEmpty(),
);
assert_eq!(
mem_op_runner
.read_bytes(SIMPLE_UART_STATUS_OFFSET, 1)
.unwrap(),
[empty_status],
);
mem_op_runner
.write_bytes(SIMPLE_UART_STATUS_OFFSET, [0], 1)
.unwrap_err();
assert_eq!(
mem_op_runner
.read_bytes(SIMPLE_UART_RECEIVE_OFFSET, 1)
.unwrap(),
[0],
);
const { assert!(SIMPLE_UART_RECEIVE_OFFSET + 1 == SIMPLE_UART_STATUS_OFFSET) };
assert_eq!(
mem_op_runner
.read_bytes(SIMPLE_UART_RECEIVE_OFFSET, 1)
.unwrap(),
[0, empty_status],
);
for i in 0..2 * BUS_WIDTH_IN_BYTES as u64 {
mem_op_runner
.read_bytes::<1>(SIMPLE_UART_USED_SIZE.get() + i, 1)
.unwrap_err();
mem_op_runner
.write_bytes(SIMPLE_UART_USED_SIZE.get() + i, [0], 1)
.unwrap_err();
}
const TEST_TEXT: &str = "Test test test test\n";
const DELAY: usize = 2;
const {
assert!(
TEST_TEXT.len() > RECEIVER_QUEUE_SIZE.get() + DELAY,
"TEST_TEXT must be long enough to make the queue overflow when accounting for DELAY"
)
};
for (i, b) in TEST_TEXT.bytes().enumerate() {
let current_count = i + 1;
mem_op_runner
.write_bytes(SIMPLE_UART_TRANSMIT_OFFSET, [b], 300)
.unwrap();
let expected_status = ReceiverQueueStatus::for_queue_len_as_u8(
current_count.saturating_sub(DELAY),
RECEIVER_QUEUE_SIZE,
);
assert_eq!(
mem_op_runner
.read_bytes(SIMPLE_UART_STATUS_OFFSET, 1)
.unwrap(),
[expected_status],
);
}
for (i, mut expected_byte) in TEST_TEXT.bytes().enumerate() {
let read = mem_op_runner
.read_bytes(SIMPLE_UART_RECEIVE_OFFSET, 1)
.unwrap();
let current_count = if i == 0 {
RECEIVER_QUEUE_SIZE.get() + 1 // initial read starts with overflow
} else {
RECEIVER_QUEUE_SIZE.get().saturating_sub(i)
};
if current_count == 0 {
expected_byte = 0;
}
let expected_status =
ReceiverQueueStatus::for_queue_len_as_u8(current_count, RECEIVER_QUEUE_SIZE);
assert_eq!(read, [expected_byte, expected_status]);
}
let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("expected/simple_uart.vcd") {
panic!();
}
}

View file

@ -0,0 +1,20 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# See Notices.txt for copyright information
[package]
name = "name_mangling_serde"
description = "serde serializer/deserializer for name mangling"
workspace = "../.."
readme = "README.md"
publish = false
edition.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
version.workspace = true
[dependencies]
serde.workspace = true
serde_json.workspace = true
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(todo)'] }

View file

@ -0,0 +1 @@
../../LICENSE.md

View file

@ -0,0 +1 @@
../../Notices.txt

View file

@ -0,0 +1,470 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use serde::{de::DeserializeOwned, Serialize};
use serde_json::{Map, Number, Value};
use std::{
fmt::{self, Write},
num::ParseIntError,
};
macro_rules! byte_enum {
(
#[repr(u8)]
$(#[$meta:meta])*
$vis:vis enum $enum:ident {
$($Variant:ident = $value:expr,)*
}
) => {
#[repr(u8)]
$(#[$meta])*
$vis enum $enum {
$($Variant = $value,)*
}
impl $enum {
$vis fn new(v: u8) -> Option<Self> {
struct Values;
#[allow(non_upper_case_globals)]
impl Values {
$(const $Variant: u8 = $enum::$Variant as u8;)*
}
match v {
$(Values::$Variant => Some(Self::$Variant),)*
_ => None,
}
}
#[allow(dead_code)]
$vis fn as_char(self) -> char {
const {
$(assert!((Self::$Variant as u8).is_ascii());)*
};
self as u8 as char
}
}
};
}
macro_rules! string_escapes {
(
$key_vis:vis enum $StringEscapeKey:ident {}
$value_vis:vis enum $StringEscapeValue:ident {
$(
#[key = $key:expr]
$Variant:ident = $value:expr,
)*
}
) => {
byte_enum! {
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
$key_vis enum $StringEscapeKey {
$($Variant = $key,)*
}
}
byte_enum! {
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
$value_vis enum $StringEscapeValue {
$($Variant = $value,)*
}
}
impl From<$StringEscapeKey> for $StringEscapeValue {
fn from(v: $StringEscapeKey) -> Self {
match v {
$($StringEscapeKey::$Variant => Self::$Variant,)*
}
}
}
impl From<$StringEscapeValue> for $StringEscapeKey {
fn from(v: $StringEscapeValue) -> Self {
match v {
$($StringEscapeValue::$Variant => Self::$Variant,)*
}
}
}
};
}
string_escapes! {
enum StringEscapeKey {}
enum StringEscapeValue {
#[key = b's']
Space = b' ',
#[key = b't']
Tab = b'\t',
#[key = b'r']
CR = b'\r',
#[key = b'n']
NewLine = b'\n',
#[key = b'_']
Underline = b'_',
}
}
fn json_string_to_name_part(value: &str, out: &mut String) {
out.push(ValuePrefix::String.as_char());
write!(out, "{}_", value.len()).unwrap();
for b in value.bytes() {
if let Some(v) = StringEscapeValue::new(b) {
out.push('_');
out.push(StringEscapeKey::from(v).as_char());
} else if b.is_ascii_alphanumeric() {
out.push(b as char);
} else {
write!(out, "_{b:02x}").unwrap()
}
}
}
byte_enum! {
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
enum ValuePrefix {
Null = b'z',
False = b'f',
True = b't',
Number = b'n',
String = b's',
Array = b'a',
Object = b'o',
}
}
fn json_value_to_name_part(value: &Value, out: &mut String) {
match value {
Value::Null => out.push(ValuePrefix::Null.as_char()),
Value::Bool(false) => out.push(ValuePrefix::False.as_char()),
Value::Bool(true) => out.push(ValuePrefix::True.as_char()),
Value::Number(number) => {
out.push(ValuePrefix::Number.as_char());
let start = out.len();
write!(out, "{number}").unwrap();
for i in start..out.len() {
out.replace_range(
i..=i,
match out.as_bytes()[i] {
b'0'..=b'9' => continue,
b'+' => "",
b'-' => "n",
b'.' => "p",
b'e' | b'E' => "e",
_ => unreachable!("invalid character in JSON number"),
},
);
}
}
Value::String(string) => json_string_to_name_part(string, out),
Value::Array(array) => {
out.push(ValuePrefix::Array.as_char());
write!(out, "{}", array.len()).unwrap();
for element in array {
json_value_to_name_part(element, out);
}
}
Value::Object(object) => {
out.push(ValuePrefix::Object.as_char());
write!(out, "{}", object.len()).unwrap();
for (k, v) in object {
json_string_to_name_part(k, out);
json_value_to_name_part(v, out);
}
}
}
}
pub const NAME_PREFIX: &str = "__HDL";
pub fn json_value_to_name(value: &Value) -> String {
let mut retval = NAME_PREFIX.into();
json_value_to_name_part(value, &mut retval);
retval
}
#[derive(Debug)]
pub enum Error {
Serde(serde_json::Error),
NameDoesNotStartWithKnownPrefix,
UnknownValuePrefix,
MissingValuePrefix,
InvalidLength(ParseIntError),
TrailingCharacters,
KeyMustBeAString,
StringMissingUnderline,
StringTruncated,
InvalidEscape,
InvalidString,
}
impl From<serde_json::Error> for Error {
fn from(value: serde_json::Error) -> Self {
Self::Serde(value)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Serde(e) => e.fmt(f),
Self::NameDoesNotStartWithKnownPrefix => {
f.write_str("name does not start with the known prefix")
}
Self::UnknownValuePrefix => f.write_str("unknown value prefix"),
Self::MissingValuePrefix => f.write_str("missing value prefix"),
Self::InvalidLength(_) => f.write_str("invalid length"),
Self::TrailingCharacters => f.write_str("trailing characters"),
Self::KeyMustBeAString => f.write_str("key must be a string"),
Self::StringMissingUnderline => f.write_str("string missing `_` after length"),
Self::StringTruncated => f.write_str("string truncated"),
Self::InvalidEscape => f.write_str("invalid escape"),
Self::InvalidString => f.write_str("invalid string"),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Serde(e) => e.source(),
Self::NameDoesNotStartWithKnownPrefix => None,
Self::UnknownValuePrefix => None,
Self::MissingValuePrefix => None,
Self::InvalidLength(e) => Some(e),
Self::TrailingCharacters => None,
Self::KeyMustBeAString => None,
Self::StringMissingUnderline => None,
Self::StringTruncated => None,
Self::InvalidEscape => None,
Self::InvalidString => None,
}
}
}
struct NameParser<'a> {
name_part: &'a str,
number_buf: String,
}
impl NameParser<'_> {
fn parse_len(&mut self) -> Result<usize, Error> {
let len_end = self
.name_part
.bytes()
.position(|b| !b.is_ascii_digit())
.unwrap_or(self.name_part.len());
let (len, rest) = self.name_part.split_at(len_end);
self.name_part = rest;
len.parse().map_err(Error::InvalidLength)
}
fn parse_string_without_prefix(&mut self) -> Result<String, Error> {
let len = self.parse_len()?;
let Some(rest) = self.name_part.strip_prefix("_") else {
return Err(Error::StringMissingUnderline);
};
self.name_part = rest;
let mut bytes = Vec::new();
for _ in 0..len {
let b = self
.name_part
.bytes()
.next()
.ok_or(Error::StringTruncated)?;
if b.is_ascii_alphanumeric() {
bytes.push(b);
self.name_part = &self.name_part[1..];
} else if b == b'_' {
self.name_part = &self.name_part[1..];
let escape = self.name_part.bytes().next().ok_or(Error::InvalidEscape)?;
self.name_part = &self.name_part[1..];
if let Some(high) = (escape as char).to_digit(16) {
let low = self
.name_part
.bytes()
.next()
.ok_or(Error::StringTruncated)?;
let low = (low as char).to_digit(16).ok_or(Error::InvalidString)?;
self.name_part = &self.name_part[1..];
bytes.push((high * 16 + low) as u8);
} else {
let escape = StringEscapeKey::new(escape).ok_or(Error::InvalidEscape)?;
bytes.push(StringEscapeValue::from(escape) as u8);
}
} else if let Some(high) = (b as char).to_digit(16) {
self.name_part = &self.name_part[1..];
let low = self
.name_part
.bytes()
.next()
.ok_or(Error::StringTruncated)?;
let low = (low as char).to_digit(16).ok_or(Error::InvalidString)?;
self.name_part = &self.name_part[1..];
bytes.push((high * 16 + low) as u8);
} else {
return Err(Error::InvalidString);
}
}
String::from_utf8(bytes).map_err(|_| Error::InvalidString)
}
fn parse_string(&mut self) -> Result<String, Error> {
if let ValuePrefix::String = self.parse_value_prefix()? {
self.parse_string_without_prefix()
} else {
Err(Error::KeyMustBeAString)
}
}
fn parse_number_without_prefix(&mut self) -> Result<Number, Error> {
let mut bytes = self.name_part.as_bytes().iter();
self.number_buf.clear();
if let Some(b'n') = bytes.clone().next() {
bytes.next();
self.number_buf.push('-');
}
while let Some(&b @ b'0'..=b'9') = bytes.clone().next() {
bytes.next();
self.number_buf.push(b as char);
}
if let Some(b'p') = bytes.clone().next() {
bytes.next();
self.number_buf.push('.');
while let Some(&b @ b'0'..=b'9') = bytes.clone().next() {
bytes.next();
self.number_buf.push(b as char);
}
}
if let Some(b'e') = bytes.clone().next() {
bytes.next();
self.number_buf.push('e');
if let Some(b'n') = bytes.clone().next() {
bytes.next();
self.number_buf.push('-');
}
while let Some(&b @ b'0'..=b'9') = bytes.clone().next() {
bytes.next();
self.number_buf.push(b as char);
}
}
self.name_part = &self.name_part[self.name_part.len() - bytes.len()..];
Ok(self.number_buf.parse()?)
}
fn parse_value_prefix(&mut self) -> Result<ValuePrefix, Error> {
let value_prefix = self
.name_part
.bytes()
.next()
.ok_or(Error::MissingValuePrefix)?;
let value_prefix = ValuePrefix::new(value_prefix).ok_or(Error::UnknownValuePrefix)?;
self.name_part = &self.name_part[1..];
Ok(value_prefix)
}
fn parse_value(&mut self) -> Result<Value, Error> {
Ok(match self.parse_value_prefix()? {
ValuePrefix::Null => Value::Null,
ValuePrefix::False => Value::Bool(false),
ValuePrefix::True => Value::Bool(true),
ValuePrefix::Number => Value::Number(self.parse_number_without_prefix()?),
ValuePrefix::String => Value::String(self.parse_string_without_prefix()?),
ValuePrefix::Array => {
let len = self.parse_len()?;
let mut array = Vec::new();
for _ in 0..len {
array.push(self.parse_value()?);
}
Value::Array(array)
}
ValuePrefix::Object => {
let len = self.parse_len()?;
let mut object = Map::new();
for _ in 0..len {
let key = self.parse_string()?;
let value = self.parse_value()?;
object.insert(key, value);
}
Value::Object(object)
}
})
}
}
pub fn name_to_json_value(name: &str) -> Result<Value, Error> {
let Some(name_part) = name.strip_prefix(NAME_PREFIX) else {
return Err(Error::NameDoesNotStartWithKnownPrefix);
};
let mut parser = NameParser {
name_part,
number_buf: String::new(),
};
let retval = parser.parse_value()?;
if !parser.name_part.is_empty() {
Err(Error::TrailingCharacters)
} else {
Ok(retval)
}
}
pub fn from_name<T: DeserializeOwned>(name: &str) -> Result<T, Error> {
Ok(serde_json::from_value(name_to_json_value(name)?)?)
}
pub fn to_name<T: Serialize>(value: T) -> Result<String, Error> {
Ok(json_value_to_name(&serde_json::to_value(value)?))
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[test]
fn test_from_to_name() {
#[track_caller]
fn check_from_to_name(value: Value, name: &str) {
assert_eq!(name, json_value_to_name(&value));
assert_eq!(
Ok(value),
name_to_json_value(name).map_err(|e| e.to_string())
);
}
check_from_to_name(json! { null }, "__HDLz");
check_from_to_name(json! { false }, "__HDLf");
check_from_to_name(json! { true }, "__HDLt");
check_from_to_name(json! { 0 }, "__HDLn0");
check_from_to_name(json! { 0.1 }, "__HDLn0p1");
check_from_to_name(json! { -0.1 }, "__HDLnn0p1");
check_from_to_name(json! { 1234567 }, "__HDLn1234567");
check_from_to_name(json! { -1.2345678e-20 }, "__HDLnn1p2345678en20");
check_from_to_name(json! { -1.2345e300 }, "__HDLnn1p2345e300");
check_from_to_name(json! { -5 }, "__HDLnn5");
check_from_to_name(json! { "" }, "__HDLs0_");
check_from_to_name(json! { "a" }, "__HDLs1_a");
check_from_to_name(json! { "A" }, "__HDLs1_A");
check_from_to_name(json! { "z" }, "__HDLs1_z");
check_from_to_name(json! { "Z" }, "__HDLs1_Z");
check_from_to_name(json! { "0" }, "__HDLs1_0");
check_from_to_name(json! { "9" }, "__HDLs1_9");
check_from_to_name(json! { "_" }, "__HDLs1___");
check_from_to_name(json! { " " }, "__HDLs1__s");
check_from_to_name(json! { "\t" }, "__HDLs1__t");
check_from_to_name(json! { "\r" }, "__HDLs1__r");
check_from_to_name(json! { "\n" }, "__HDLs1__n");
check_from_to_name(json! { "\u{25}" }, "__HDLs1__25");
check_from_to_name(json! { "\u{100}" }, "__HDLs2__c4_80");
check_from_to_name(json! { "\u{1000}" }, "__HDLs3__e1_80_80");
check_from_to_name(json! { "\u{10000}" }, "__HDLs4__f0_90_80_80");
check_from_to_name(json! { "foo" }, "__HDLs3_foo");
check_from_to_name(json! { { "foo": 123 } }, "__HDLo1s3_foon123");
check_from_to_name(
json! { { "foo": 123, "bar": null } },
"__HDLo2s3_foon123s3_barz",
);
check_from_to_name(json! { [1, 2, 3, 4] }, "__HDLa4n1n2n3n4");
check_from_to_name(
json! { { "a": [], "b": null, "c": 1234, "d": {} } },
"__HDLo4s1_aa0s1_bzs1_cn1234s1_do0",
);
}
}

View file

@ -31,7 +31,6 @@ function check_file()
POUND_HEADER=('^"# SPDX-License-Identifier: LGPL-3.0-or-later"$' '^"# See Notices.txt for copyright information"$')
SLASH_HEADER=('^"// SPDX-License-Identifier: LGPL-3.0-or-later"$' '^"// See Notices.txt for copyright information"$')
MD_HEADER=('^"<!--"$' '^"SPDX-License-Identifier: LGPL-3.0-or-later"$' '^"See Notices.txt for copyright information"$')
MERMAID_HEADER=('^"%% SPDX-License-Identifier: LGPL-3.0-or-later"$' '^"%% See Notices.txt for copyright information"$')
function main()
{
@ -46,15 +45,12 @@ function main()
*/LICENSE.md|*/Notices.txt)
# copyright file
;;
/crates/cpu/tests*/expected/*.vcd|/crates/cpu/tests*/expected/*.txt)
/crates/cpu/tests/expected/*.vcd|/crates/cpu/tests/expected/*.txt)
# file that can't contain copyright header
;;
/.forgejo/workflows/*.yml|*/.gitignore|*/.gitattributes|*.toml)
/.forgejo/workflows/*.yml|*/.gitignore|*.toml)
check_file "$file" "${POUND_HEADER[@]}"
;;
*.mermaid)
check_file "$file" "${MERMAID_HEADER[@]}"
;;
*.md)
check_file "$file" "${MD_HEADER[@]}"
;;
@ -71,4 +67,4 @@ function main()
done
}
main
main