Compare commits
4 commits
master
...
wip-splitt
| Author | SHA1 | Date | |
|---|---|---|---|
| 921cddbe06 | |||
| b9d68c0019 | |||
| 9fa959652e | |||
| 9199fdf35c |
79 changed files with 35963 additions and 1819884 deletions
77
.forgejo/workflows/deps.yml
Normal file
77
.forgejo/workflows/deps.yml
Normal 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 }}
|
||||||
|
|
@ -3,18 +3,56 @@
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
deps:
|
||||||
|
uses: ./.forgejo/workflows/deps.yml
|
||||||
test:
|
test:
|
||||||
runs-on: debian-12
|
runs-on: debian-12
|
||||||
container:
|
needs: deps
|
||||||
image: git.libre-chip.org/libre-chip/fayalite-deps:latest
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: https://git.libre-chip.org/mirrors/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- run: |
|
- run: |
|
||||||
scripts/check-copyright.sh
|
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
|
- uses: https://git.libre-chip.org/mirrors/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/master' }}
|
save-if: ${{ github.ref == 'refs/heads/master' }}
|
||||||
- run: rustup override set 1.93.0
|
|
||||||
- run: cargo test
|
- run: cargo test
|
||||||
|
|
|
||||||
3
.gitattributes
vendored
3
.gitattributes
vendored
|
|
@ -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
1
.gitignore
vendored
|
|
@ -1,4 +1,3 @@
|
||||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
# See Notices.txt for copyright information
|
# See Notices.txt for copyright information
|
||||||
/target
|
/target
|
||||||
OPF_PowerISA_v3.1C.pdf
|
|
||||||
|
|
|
||||||
611
Cargo.lock
generated
611
Cargo.lock
generated
|
|
@ -1,20 +1,17 @@
|
||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 3
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "adler2"
|
name = "ahash"
|
||||||
version = "2.0.1"
|
version = "0.8.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aho-corasick"
|
|
||||||
version = "1.1.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
"version_check",
|
||||||
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -96,36 +93,6 @@ version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
|
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]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.6.0"
|
version = "2.6.0"
|
||||||
|
|
@ -168,48 +135,21 @@ dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bytes"
|
|
||||||
version = "1.11.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.51"
|
version = "1.1.28"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203"
|
checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
|
||||||
"shlex",
|
"shlex",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cexpr"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
|
||||||
dependencies = [
|
|
||||||
"nom",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
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]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.20"
|
version = "4.5.20"
|
||||||
|
|
@ -232,15 +172,6 @@ dependencies = [
|
||||||
"strsim",
|
"strsim",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap_complete"
|
|
||||||
version = "4.5.59"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2348487adcd4631696ced64ccdb40d38ac4d31cae7f2eec8817fcea1b9d1c43c"
|
|
||||||
dependencies = [
|
|
||||||
"clap",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.5.18"
|
version = "4.5.18"
|
||||||
|
|
@ -275,17 +206,9 @@ checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
|
||||||
name = "cpu"
|
name = "cpu"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base16ct 1.0.0",
|
|
||||||
"fayalite",
|
"fayalite",
|
||||||
"hex-literal",
|
"name_mangling_serde",
|
||||||
"parse_powerisa_pdf",
|
|
||||||
"regex",
|
|
||||||
"roxmltree",
|
|
||||||
"serde",
|
"serde",
|
||||||
"sha2",
|
|
||||||
"simple-mermaid",
|
|
||||||
"ureq",
|
|
||||||
"which",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -297,15 +220,6 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crc32fast"
|
|
||||||
version = "1.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
|
|
@ -361,12 +275,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
version = "0.3.14"
|
version = "0.3.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -388,27 +302,23 @@ checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fayalite"
|
name = "fayalite"
|
||||||
version = "0.3.0"
|
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 = [
|
dependencies = [
|
||||||
"base64",
|
|
||||||
"bitvec",
|
"bitvec",
|
||||||
"blake3",
|
"blake3",
|
||||||
"clap",
|
"clap",
|
||||||
"clap_complete",
|
|
||||||
"ctor",
|
"ctor",
|
||||||
"eyre",
|
"eyre",
|
||||||
"fayalite-proc-macros",
|
"fayalite-proc-macros",
|
||||||
"fayalite-visit-gen",
|
"fayalite-visit-gen",
|
||||||
"hashbrown 0.15.5",
|
"hashbrown",
|
||||||
"jobslot",
|
"jobslot",
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"once_cell",
|
"os_pipe",
|
||||||
"ordered-float",
|
|
||||||
"petgraph",
|
"petgraph",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"vec_map",
|
"vec_map",
|
||||||
"which",
|
"which",
|
||||||
|
|
@ -417,7 +327,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fayalite-proc-macros"
|
name = "fayalite-proc-macros"
|
||||||
version = "0.3.0"
|
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 = [
|
dependencies = [
|
||||||
"fayalite-proc-macros-impl",
|
"fayalite-proc-macros-impl",
|
||||||
]
|
]
|
||||||
|
|
@ -425,9 +335,9 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fayalite-proc-macros-impl"
|
name = "fayalite-proc-macros-impl"
|
||||||
version = "0.3.0"
|
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 = [
|
dependencies = [
|
||||||
"base16ct 0.2.0",
|
"base16ct",
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
"prettyplease",
|
"prettyplease",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|
@ -440,7 +350,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fayalite-visit-gen"
|
name = "fayalite-visit-gen"
|
||||||
version = "0.3.0"
|
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 = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"prettyplease",
|
"prettyplease",
|
||||||
|
|
@ -452,34 +362,12 @@ dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "find-msvc-tools"
|
|
||||||
version = "0.1.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fixedbitset"
|
name = "fixedbitset"
|
||||||
version = "0.5.7"
|
version = "0.5.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
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]]
|
[[package]]
|
||||||
name = "funty"
|
name = "funty"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
|
|
@ -498,62 +386,31 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.16"
|
version = "0.2.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"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]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.15.5"
|
version = "0.14.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
"allocator-api2",
|
"allocator-api2",
|
||||||
"equivalent",
|
|
||||||
"foldhash",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashbrown"
|
|
||||||
version = "0.16.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hex-literal"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "home"
|
name = "home"
|
||||||
version = "0.5.9"
|
version = "0.5.9"
|
||||||
|
|
@ -563,22 +420,6 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"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]]
|
[[package]]
|
||||||
name = "indenter"
|
name = "indenter"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
|
|
@ -587,14 +428,13 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.13.0"
|
version = "2.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.16.1",
|
"hashbrown",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_core",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -603,15 +443,6 @@ version = "1.70.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itertools"
|
|
||||||
version = "0.13.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
|
||||||
dependencies = [
|
|
||||||
"either",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.11"
|
version = "1.0.11"
|
||||||
|
|
@ -620,39 +451,23 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jobslot"
|
name = "jobslot"
|
||||||
version = "0.2.23"
|
version = "0.2.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "58715c67c327da7f1558708348d68c207fd54900c4ae0529e29305d04d795b8c"
|
checksum = "fe10868679d7a24c2c67d862d0e64a342ce9aef7cdde9ce8019bd35d353d458d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"derive_destructure2",
|
"derive_destructure2",
|
||||||
"getrandom 0.3.4",
|
"getrandom",
|
||||||
"libc",
|
"libc",
|
||||||
"scopeguard",
|
"scopeguard",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.180"
|
version = "0.2.159"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
|
||||||
|
|
||||||
[[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"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
|
|
@ -660,12 +475,6 @@ version = "0.4.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
|
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "log"
|
|
||||||
version = "0.4.29"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.4"
|
version = "2.7.4"
|
||||||
|
|
@ -673,42 +482,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "minimal-lexical"
|
name = "name_mangling_serde"
|
||||||
version = "0.2.1"
|
version = "0.1.0"
|
||||||
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"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"adler2",
|
"serde",
|
||||||
"simd-adler32",
|
"serde_json",
|
||||||
]
|
|
||||||
|
|
||||||
[[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",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -741,56 +519,29 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.3"
|
version = "1.20.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ordered-float"
|
name = "os_pipe"
|
||||||
version = "5.1.0"
|
version = "1.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d"
|
checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-traits",
|
"libc",
|
||||||
"rand",
|
"windows-sys 0.59.0",
|
||||||
"serde",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[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]]
|
[[package]]
|
||||||
name = "petgraph"
|
name = "petgraph"
|
||||||
version = "0.8.3"
|
version = "0.6.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/programmerjake/petgraph.git?rev=258ea8071209a924b73fe96f9f87a3b7b45cbc9f#258ea8071209a924b73fe96f9f87a3b7b45cbc9f"
|
||||||
checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fixedbitset",
|
"fixedbitset",
|
||||||
"hashbrown 0.15.5",
|
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"serde",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pkg-config"
|
|
||||||
version = "0.3.32"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prettyplease"
|
name = "prettyplease"
|
||||||
version = "0.2.22"
|
version = "0.2.22"
|
||||||
|
|
@ -810,15 +561,6 @@ dependencies = [
|
||||||
"unicode-ident",
|
"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]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.37"
|
version = "1.0.37"
|
||||||
|
|
@ -828,95 +570,12 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "r-efi"
|
|
||||||
version = "5.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "radium"
|
name = "radium"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
|
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]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.38.37"
|
version = "0.38.37"
|
||||||
|
|
@ -930,41 +589,6 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"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]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
|
|
@ -979,28 +603,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.228"
|
version = "1.0.210"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
|
||||||
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"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.228"
|
version = "1.0.210"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -1022,9 +636,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha2"
|
name = "sha2"
|
||||||
version = "0.10.9"
|
version = "0.10.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cpufeatures",
|
"cpufeatures",
|
||||||
|
|
@ -1037,30 +651,12 @@ version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
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]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "subtle"
|
|
||||||
version = "2.6.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.96"
|
version = "2.0.96"
|
||||||
|
|
@ -1123,47 +719,6 @@ version = "1.0.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
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]]
|
[[package]]
|
||||||
name = "utf8parse"
|
name = "utf8parse"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
|
@ -1184,27 +739,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
[[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",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "which"
|
name = "which"
|
||||||
|
|
@ -1214,17 +751,10 @@ checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"either",
|
"either",
|
||||||
"home",
|
"home",
|
||||||
"regex",
|
|
||||||
"rustix",
|
"rustix",
|
||||||
"winsafe",
|
"winsafe",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-link"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
|
|
@ -1243,15 +773,6 @@ dependencies = [
|
||||||
"windows-targets",
|
"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]]
|
[[package]]
|
||||||
name = "windows-targets"
|
name = "windows-targets"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
|
|
@ -1322,12 +843,6 @@ version = "0.0.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
|
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wit-bindgen"
|
|
||||||
version = "0.46.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wyz"
|
name = "wyz"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
|
@ -1339,26 +854,20 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.27"
|
version = "0.7.35"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
|
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy-derive",
|
"zerocopy-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy-derive"
|
||||||
version = "0.8.27"
|
version = "0.7.35"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
|
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zeroize"
|
|
||||||
version = "1.8.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
|
||||||
|
|
|
||||||
14
Cargo.toml
14
Cargo.toml
|
|
@ -7,23 +7,17 @@ members = ["crates/*"]
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
license = "LGPL-3.0-or-later"
|
license = "LGPL-3.0-or-later"
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
repository = ""
|
repository = ""
|
||||||
keywords = []
|
keywords = []
|
||||||
categories = []
|
categories = []
|
||||||
rust-version = "1.93.0"
|
rust-version = "1.82.0"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[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" }
|
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"] }
|
serde = { version = "1.0.202", features = ["derive"] }
|
||||||
sha2 = "0.10.9"
|
serde_json = { version = "1.0.117", features = ["preserve_order"] }
|
||||||
simple-mermaid = "0.2.0"
|
|
||||||
ureq = "3.1.4"
|
|
||||||
which = { version = "6.0.3", features = ["regex"] }
|
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 1
|
opt-level = 1
|
||||||
|
|
|
||||||
15
README.md
15
README.md
|
|
@ -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 № [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).
|
|
||||||
|
|
@ -16,23 +16,8 @@ version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
fayalite.workspace = true
|
fayalite.workspace = true
|
||||||
roxmltree.workspace = true
|
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
simple-mermaid.workspace = true
|
name_mangling_serde.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
|
|
||||||
|
|
||||||
[lints.rust]
|
[lints.rust]
|
||||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(todo)'] }
|
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(todo)'] }
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
../../README.md
|
|
||||||
|
|
@ -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(())
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +1,17 @@
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
use crate::{instruction::CONST_ZERO_UNIT_NUM, unit::UnitKind};
|
use crate::{
|
||||||
use fayalite::{expr::HdlPartialOrdImpl, prelude::*};
|
instruction::{PRegNum, CONST_ZERO_UNIT_NUM},
|
||||||
|
unit::UnitKind,
|
||||||
|
};
|
||||||
|
use fayalite::{
|
||||||
|
intern::{Intern, Interned},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
#[derive(Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct UnitConfig {
|
pub struct UnitConfig {
|
||||||
pub kind: UnitKind,
|
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]
|
#[non_exhaustive]
|
||||||
pub struct CpuConfig {
|
pub struct CpuConfig {
|
||||||
pub units: Vec<UnitConfig>,
|
pub units: Interned<[UnitConfig]>,
|
||||||
pub out_reg_num_width: usize,
|
pub out_reg_num_width: usize,
|
||||||
pub fetch_width: NonZeroUsize,
|
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`]
|
/// default value for [`UnitConfig::max_in_flight`]
|
||||||
pub default_unit_max_in_flight: NonZeroUsize,
|
pub default_unit_max_in_flight: NonZeroUsize,
|
||||||
pub rob_size: NonZeroUsize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CpuConfig {
|
impl CpuConfig {
|
||||||
|
|
@ -47,46 +46,18 @@ impl CpuConfig {
|
||||||
};
|
};
|
||||||
v
|
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 = {
|
pub const DEFAULT_UNIT_MAX_IN_FLIGHT: NonZeroUsize = {
|
||||||
let Some(v) = NonZeroUsize::new(8) else {
|
let Some(v) = NonZeroUsize::new(8) else {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
};
|
};
|
||||||
v
|
v
|
||||||
};
|
};
|
||||||
pub fn new(units: Vec<UnitConfig>, rob_size: NonZeroUsize) -> Self {
|
pub fn new(units: Interned<[UnitConfig]>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
units,
|
units,
|
||||||
out_reg_num_width: Self::DEFAULT_OUT_REG_NUM_WIDTH,
|
out_reg_num_width: Self::DEFAULT_OUT_REG_NUM_WIDTH,
|
||||||
fetch_width: Self::DEFAULT_FETCH_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,
|
default_unit_max_in_flight: Self::DEFAULT_UNIT_MAX_IN_FLIGHT,
|
||||||
rob_size,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn non_const_unit_nums(&self) -> std::ops::Range<usize> {
|
pub fn non_const_unit_nums(&self) -> std::ops::Range<usize> {
|
||||||
|
|
@ -95,135 +66,83 @@ impl CpuConfig {
|
||||||
pub fn unit_num_width(&self) -> usize {
|
pub fn unit_num_width(&self) -> usize {
|
||||||
UInt::range(CONST_ZERO_UNIT_NUM..self.non_const_unit_nums().end).width()
|
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 {
|
pub fn unit_max_in_flight(&self, unit_index: usize) -> NonZeroUsize {
|
||||||
self.units[unit_index]
|
self.units[unit_index]
|
||||||
.max_in_flight
|
.max_in_flight
|
||||||
.unwrap_or(self.default_unit_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 retire_queue_index_width(&self) -> usize {
|
||||||
pub fn max_unit_max_in_flight(&self) -> NonZeroUsize {
|
let max_in_flight: usize = (0..self.units.len())
|
||||||
(0..self.units.len())
|
.map(|unit_index| self.unit_max_in_flight(unit_index).get())
|
||||||
.map(|unit_index| self.unit_max_in_flight(unit_index))
|
.sum();
|
||||||
.max()
|
2 + max_in_flight.next_power_of_two().ilog2() as usize
|
||||||
.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")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[hdl(get(|c| c.out_reg_num_width))]
|
mod sealed {
|
||||||
pub type CpuConfigOutRegNumWidth<C: PhantomConstGet<CpuConfig>> = DynSize;
|
pub trait Sealed {}
|
||||||
|
|
||||||
#[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>
|
|
||||||
{
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -25,514 +25,28 @@ pub struct PowerIsaCrBitNum {
|
||||||
pub bit_in_field: UInt<2>,
|
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 {
|
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 {
|
pub const fn power_isa_cr_reg_num(index: usize) -> u32 {
|
||||||
if index == 0 {
|
range_u32_nth_or_panic(&Self::POWER_ISA_CR_REG_NUMS, index)
|
||||||
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<_>>(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn power_isa_gpr_or_zero_reg_num(index: usize) -> u32 {
|
pub const POWER_ISA_GPR_REG_NUMS: Range<u32> = 32..64;
|
||||||
if index == 0 {
|
pub const fn power_isa_gpr_reg_num(index: usize) -> u32 {
|
||||||
Self::CONST_ZERO_REG_NUM
|
range_u32_nth_or_panic(&Self::POWER_ISA_GPR_REG_NUMS, index)
|
||||||
} else {
|
|
||||||
Self::power_isa_gpr_reg_num(index)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#[hdl]
|
|
||||||
pub fn power_isa_gpr_or_zero_reg(reg_num: Expr<UInt<5>>) -> Expr<Self> {
|
pub const POWER_ISA_FPR_REG_NUMS: Range<u32> = 64..96;
|
||||||
#[hdl]
|
pub const fn power_isa_fpr_reg_num(index: usize) -> u32 {
|
||||||
let power_isa_gpr_or_zero_reg: Self = wire();
|
range_u32_nth_or_panic(&Self::POWER_ISA_FPR_REG_NUMS, index)
|
||||||
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<_>>(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
261
crates/cpu/src/instruction_rename.rs
Normal file
261
crates/cpu/src/instruction_rename.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,11 @@
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod decoder;
|
|
||||||
pub mod fetch;
|
|
||||||
pub mod instruction;
|
pub mod instruction;
|
||||||
pub mod main_memory_and_io;
|
pub mod instruction_rename;
|
||||||
pub mod next_pc;
|
|
||||||
pub mod powerisa_instructions_xml;
|
|
||||||
#[cfg(todo)]
|
|
||||||
pub mod reg_alloc;
|
pub mod reg_alloc;
|
||||||
pub mod register;
|
pub mod register;
|
||||||
pub mod rename_execute_retire;
|
pub mod rename_table;
|
||||||
|
pub mod retire_queue;
|
||||||
pub mod unit;
|
pub mod unit;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
|
@ -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
|
|
@ -3,18 +3,18 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
config::CpuConfig,
|
config::CpuConfig,
|
||||||
instruction::{
|
instruction::{
|
||||||
COMMON_MOP_SRC_LEN, MOp, MOpDestReg, MOpRegNum, MOpTrait, MoveRegMOp, PRegNum,
|
MOp, MOpDestReg, MOpRegNum, MOpTrait, MoveRegMOp, PRegNum, UnitOutRegNum,
|
||||||
RenameTableName, UnitOutRegNum,
|
COMMON_MOP_SRC_LEN,
|
||||||
},
|
},
|
||||||
unit::{
|
unit::{
|
||||||
GlobalState, TrapData, UnitMOp, UnitOutput, UnitOutputWrite, UnitResult,
|
GlobalState, TrapData, UnitMOp, UnitOutput, UnitOutputWrite, UnitResult,
|
||||||
UnitResultCompleted, UnitTrait,
|
UnitResultCompleted, UnitTrait,
|
||||||
unit_base::{UnitForwardingInfo, UnitInput},
|
|
||||||
},
|
},
|
||||||
util::tree_reduce::tree_reduce_with_state,
|
util::array_vec::ReadyValidArray,
|
||||||
};
|
};
|
||||||
use fayalite::{
|
use fayalite::{
|
||||||
memory::{WriteStruct, splat_mask},
|
int::BoolOrIntType,
|
||||||
|
memory::{splat_mask, WriteStruct},
|
||||||
module::{instance_with_loc, memory_with_loc, wire_with_loc},
|
module::{instance_with_loc, memory_with_loc, wire_with_loc},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
util::ready_valid::ReadyValid,
|
util::ready_valid::ReadyValid,
|
||||||
|
|
@ -44,150 +44,12 @@ pub enum FetchDecodeSpecialOp {
|
||||||
|
|
||||||
#[hdl]
|
#[hdl]
|
||||||
pub struct FetchDecodeInterface<FetchWidth: Size> {
|
pub struct FetchDecodeInterface<FetchWidth: Size> {
|
||||||
pub decoded_insns: ArrayType<ReadyValid<FetchedDecodedMOp>, FetchWidth>,
|
pub decoded_insns: ReadyValidArray<FetchedDecodedMOp, FetchWidth>,
|
||||||
#[hdl(flip)]
|
#[hdl(flip)]
|
||||||
pub fetch_decode_special_op: ReadyValid<FetchDecodeSpecialOp>,
|
pub fetch_decode_special_op: ReadyValid<FetchDecodeSpecialOp>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[hdl]
|
#[cfg(todo)]
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[hdl_module]
|
#[hdl_module]
|
||||||
/// combination register allocator, register renaming, unit selection, and retire handling
|
/// combination register allocator, register renaming, unit selection, and retire handling
|
||||||
pub fn reg_alloc(config: &CpuConfig) {
|
pub fn reg_alloc(config: &CpuConfig) {
|
||||||
|
|
@ -205,10 +67,6 @@ pub fn reg_alloc(config: &CpuConfig) {
|
||||||
);
|
);
|
||||||
// TODO: finish
|
// TODO: finish
|
||||||
|
|
||||||
#[hdl]
|
|
||||||
let rob = instance(rob(config));
|
|
||||||
connect(rob.cd, cd);
|
|
||||||
|
|
||||||
let mut rename_table_mems = BTreeMap::<RenameTableName, MemBuilder<_>>::new();
|
let mut rename_table_mems = BTreeMap::<RenameTableName, MemBuilder<_>>::new();
|
||||||
|
|
||||||
for reg_kind in MOpDestReg::REG_KINDS {
|
for reg_kind in MOpDestReg::REG_KINDS {
|
||||||
|
|
@ -238,11 +96,6 @@ pub fn reg_alloc(config: &CpuConfig) {
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let renamed_mops_out_reg = wire(Array[HdlOption[config.p_reg_num()]][config.fetch_width.get()]);
|
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() {
|
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
|
// TODO: finish
|
||||||
connect(
|
connect(
|
||||||
fetch_decode_interface.decoded_insns[fetch_index].ready,
|
fetch_decode_interface.decoded_insns[fetch_index].ready,
|
||||||
|
|
@ -263,7 +116,7 @@ pub fn reg_alloc(config: &CpuConfig) {
|
||||||
);
|
);
|
||||||
connect(
|
connect(
|
||||||
renamed_mops[fetch_index],
|
renamed_mops[fetch_index],
|
||||||
renamed_mops.ty().element().HdlNone(),
|
Expr::ty(renamed_mops).element().HdlNone(),
|
||||||
);
|
);
|
||||||
#[hdl]
|
#[hdl]
|
||||||
struct RenameTableReadPort<T> {
|
struct RenameTableReadPort<T> {
|
||||||
|
|
@ -332,7 +185,7 @@ pub fn reg_alloc(config: &CpuConfig) {
|
||||||
let write_port = wire_with_loc(
|
let write_port = wire_with_loc(
|
||||||
&format!("{table_name}_{fetch_index}_{}", reg_kind.reg_name()),
|
&format!("{table_name}_{fetch_index}_{}", reg_kind.reg_name()),
|
||||||
SourceLocation::caller(),
|
SourceLocation::caller(),
|
||||||
write_port_.ty(),
|
Expr::ty(write_port_),
|
||||||
);
|
);
|
||||||
connect(write_port_, write_port);
|
connect(write_port_, write_port);
|
||||||
write_ports.push_back(write_port);
|
write_ports.push_back(write_port);
|
||||||
|
|
@ -343,7 +196,7 @@ pub fn reg_alloc(config: &CpuConfig) {
|
||||||
addr: 0_hdl_u0,
|
addr: 0_hdl_u0,
|
||||||
en: false,
|
en: false,
|
||||||
clk: cd.clk,
|
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()),
|
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,
|
config.renamed_mop_in_unit().TransformedMove,
|
||||||
|renamed_mop, renamed_move_op: Expr<MoveRegMOp<_, _>>| {
|
|renamed_mop, renamed_move_op: Expr<MoveRegMOp<_, _>>| {
|
||||||
// TODO: finish handling MoveRegMOp
|
// TODO: finish handling MoveRegMOp
|
||||||
connect(renamed_mop, renamed_mop.ty().HdlNone());
|
connect(renamed_mop, Expr::ty(renamed_mop).HdlNone());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
connect(
|
connect(
|
||||||
|
|
@ -429,7 +282,7 @@ pub fn reg_alloc(config: &CpuConfig) {
|
||||||
);
|
);
|
||||||
connect(
|
connect(
|
||||||
selected_unit_index_leaf,
|
selected_unit_index_leaf,
|
||||||
selected_unit_index_leaf.ty().HdlNone(),
|
Expr::ty(selected_unit_index_leaf).HdlNone(),
|
||||||
);
|
);
|
||||||
let unit_index_wire = wire_with_loc(
|
let unit_index_wire = wire_with_loc(
|
||||||
&format!("unit_index_{fetch_index}_{unit_index}"),
|
&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(
|
let selected_unit_index_node = wire_with_loc(
|
||||||
&format!("selected_unit_index_node_{fetch_index}_{state}"),
|
&format!("selected_unit_index_node_{fetch_index}_{state}"),
|
||||||
SourceLocation::caller(),
|
SourceLocation::caller(),
|
||||||
l.ty(),
|
Expr::ty(l),
|
||||||
);
|
);
|
||||||
*state += 1;
|
*state += 1;
|
||||||
connect(selected_unit_index_node, l);
|
connect(selected_unit_index_node, l);
|
||||||
|
|
@ -483,7 +336,6 @@ pub fn reg_alloc(config: &CpuConfig) {
|
||||||
);
|
);
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let unit_forwarding_info = wire(config.unit_forwarding_info());
|
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() {
|
for (unit_index, unit_config) in config.units.iter().enumerate() {
|
||||||
let dyn_unit = unit_config.kind.unit(config, unit_index);
|
let dyn_unit = unit_config.kind.unit(config, unit_index);
|
||||||
let unit = instance_with_loc(
|
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_free_regs_tracker.alloc_out[0].ready, false);
|
||||||
connect(
|
connect(
|
||||||
unit_to_reg_alloc.input.data,
|
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() {
|
for fetch_index in 0..config.fetch_width.get() {
|
||||||
#[hdl]
|
#[hdl]
|
||||||
|
|
@ -550,7 +402,7 @@ pub fn reg_alloc(config: &CpuConfig) {
|
||||||
} else {
|
} else {
|
||||||
connect(
|
connect(
|
||||||
unit_to_reg_alloc.input.data,
|
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(), "");
|
// 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_to_reg_alloc.unit_forwarding_info, unit_forwarding_info);
|
||||||
connect(
|
connect(
|
||||||
unit_forwarding_info.unit_output_writes[unit_index],
|
unit_forwarding_info.unit_output_writes[unit_index],
|
||||||
unit_forwarding_info
|
Expr::ty(unit_forwarding_info)
|
||||||
.ty()
|
|
||||||
.unit_output_writes
|
.unit_output_writes
|
||||||
.element()
|
.element()
|
||||||
.HdlNone(),
|
.HdlNone(),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
use crate::util::tree_reduce::tree_reduce;
|
use fayalite::{
|
||||||
use fayalite::{module::wire_with_loc, prelude::*, util::ready_valid::ReadyValid};
|
module::wire_with_loc,
|
||||||
|
prelude::*,
|
||||||
|
util::{prefix_sum::reduce, ready_valid::ReadyValid},
|
||||||
|
};
|
||||||
use std::{num::NonZeroUsize, ops::Range};
|
use std::{num::NonZeroUsize, ops::Range};
|
||||||
|
|
||||||
#[hdl_module]
|
#[hdl_module]
|
||||||
|
|
@ -44,7 +47,7 @@ pub fn unit_free_regs_tracker(
|
||||||
count,
|
count,
|
||||||
count_overflowed,
|
count_overflowed,
|
||||||
alloc_nums,
|
alloc_nums,
|
||||||
}) = tree_reduce(
|
}) = reduce(
|
||||||
(0..reg_count).map(|index| Summary {
|
(0..reg_count).map(|index| Summary {
|
||||||
range: index..index + 1,
|
range: index..index + 1,
|
||||||
count: (!allocated_reg[index])
|
count: (!allocated_reg[index])
|
||||||
|
|
@ -73,7 +76,7 @@ pub fn unit_free_regs_tracker(
|
||||||
let reduced_alloc_nums = wire_with_loc(
|
let reduced_alloc_nums = wire_with_loc(
|
||||||
&format!("reduced_alloc_nums_{}_{}", range.start, range.end),
|
&format!("reduced_alloc_nums_{}_{}", range.start, range.end),
|
||||||
SourceLocation::caller(),
|
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() {
|
for alloc_index in 0..alloc_at_once.get() {
|
||||||
#[hdl]
|
#[hdl]
|
||||||
|
|
@ -120,7 +123,10 @@ pub fn unit_free_regs_tracker(
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
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;
|
use std::num::NonZero;
|
||||||
|
|
||||||
fn test_unit_free_regs_tracker(
|
fn test_unit_free_regs_tracker(
|
||||||
|
|
@ -195,7 +201,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[hdl]
|
#[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);
|
connect(free_before_alloc_array[0], free_reg);
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let expected_alloc = wire(Array[HdlOption[reg_num_ty]][alloc_at_once.get()]);
|
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
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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 µOps that this unrenamed µ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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
187
crates/cpu/src/rename_table.rs
Normal file
187
crates/cpu/src/rename_table.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
33
crates/cpu/src/retire_queue.rs
Normal file
33
crates/cpu/src/retire_queue.rs
Normal 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!();
|
||||||
|
}
|
||||||
|
|
@ -2,34 +2,36 @@
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::CpuConfig,
|
config::{CpuConfig, CpuConfigType, RetireQueueIndexWidth, UnitCount},
|
||||||
instruction::{
|
instruction::{
|
||||||
AluBranchMOp, LoadStoreMOp, MOp, MOpDestReg, MOpInto, MOpRegNum, MOpTrait,
|
mop_enum, AluBranchMOp, LoadStoreMOp, MOp, MOpDestReg, MOpInto, MOpRegNum, MOpTrait,
|
||||||
MOpVariantVisitOps, MOpVariantVisitor, MOpVisitVariants, RenamedMOp, mop_enum,
|
RenamedMOp, UnitOutRegNum,
|
||||||
},
|
},
|
||||||
rename_execute_retire::ExecuteToUnitInterface,
|
register::{FlagsMode, PRegValue},
|
||||||
};
|
};
|
||||||
use fayalite::{
|
use fayalite::{
|
||||||
bundle::{Bundle, BundleType},
|
bundle::{Bundle, BundleType},
|
||||||
|
int::BoolOrIntType,
|
||||||
intern::{Intern, Interned},
|
intern::{Intern, Interned},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
|
util::ready_valid::ReadyValid,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::ops::ControlFlow;
|
|
||||||
|
|
||||||
pub mod alu_branch;
|
pub mod alu_branch;
|
||||||
|
pub mod unit_base;
|
||||||
|
|
||||||
macro_rules! all_units {
|
macro_rules! all_units {
|
||||||
(
|
(
|
||||||
#[hdl_unit_kind = $HdlUnitKind:ident]
|
#[hdl_unit_kind = $HdlUnitKind:ident]
|
||||||
#[unit_kind = $UnitKind:ident]
|
#[unit_kind = $UnitKind:ident]
|
||||||
#[hdl(custom_debug(sim))]
|
#[hdl]
|
||||||
$(#[$enum_meta:meta])*
|
$(#[$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)*])?
|
$(#[transformed_move $($transformed_move:tt)*])?
|
||||||
#[create_dyn_unit_fn = $create_dyn_unit_fn:expr]
|
#[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])*
|
$(#[$variant_meta:meta])*
|
||||||
$Unit:ident($Op:ty),
|
$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 {
|
impl ToExpr for $UnitKind {
|
||||||
|
type Type = $HdlUnitKind;
|
||||||
|
|
||||||
fn to_expr(&self) -> Expr<Self::Type> {
|
fn to_expr(&self) -> Expr<Self::Type> {
|
||||||
match self {
|
match self {
|
||||||
$($UnitKind::$Unit => $HdlUnitKind.$Unit(),)*
|
$($UnitKind::$Unit => $HdlUnitKind.$Unit(),)*
|
||||||
|
|
@ -80,17 +75,9 @@ macro_rules! all_units {
|
||||||
|
|
||||||
mop_enum! {
|
mop_enum! {
|
||||||
#[impl_mop_into = false]
|
#[impl_mop_into = false]
|
||||||
#[hdl(custom_debug(sim))]
|
#[hdl]
|
||||||
$(#[$enum_meta])*
|
$(#[$enum_meta])*
|
||||||
$vis enum $UnitMOpEnum<
|
$vis enum $UnitMOpEnum<$DestReg: Type, $SrcRegWidth: Size, #[MOp(get_ty = $transformed_move_op_get_ty)] $TransformedMoveOp: Type> {
|
||||||
$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>,)*
|
|
||||||
]
|
|
||||||
> {
|
|
||||||
$(
|
$(
|
||||||
$(#[$variant_meta])*
|
$(#[$variant_meta])*
|
||||||
$Unit($Op),
|
$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]
|
#[hdl]
|
||||||
$vis fn kind(expr: impl ToExpr<Type = Self>) -> Expr<$HdlUnitKind> {
|
$vis fn kind(expr: impl ToExpr<Type = Self>) -> Expr<$HdlUnitKind> {
|
||||||
#[hdl]
|
#[hdl]
|
||||||
|
|
@ -109,20 +96,11 @@ macro_rules! all_units {
|
||||||
}
|
}
|
||||||
unit_kind
|
unit_kind
|
||||||
}
|
}
|
||||||
#[hdl]
|
|
||||||
$vis fn kind_sim(expr: &SimValue<Self>) -> UnitKind {
|
|
||||||
#![allow(unreachable_patterns)]
|
|
||||||
#[hdl(sim)]
|
|
||||||
match expr {
|
|
||||||
$(Self::$Unit(_) => $UnitKind::$Unit,)*
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$(
|
$(
|
||||||
#[hdl]
|
#[hdl]
|
||||||
$vis fn $extract(expr: impl ToExpr<Type = Self>) -> Expr<HdlOption<$Op>> {
|
$vis fn $extract(expr: impl ToExpr<Type = Self>) -> Expr<HdlOption<$Op>> {
|
||||||
let expr = expr.to_expr();
|
let expr = expr.to_expr();
|
||||||
let ty = expr.ty();
|
let ty = Expr::ty(expr);
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let $extract = wire(HdlOption[ty.$Unit]);
|
let $extract = wire(HdlOption[ty.$Unit]);
|
||||||
connect($extract, HdlOption[ty.$Unit].HdlNone());
|
connect($extract, HdlOption[ty.$Unit].HdlNone());
|
||||||
|
|
@ -132,47 +110,19 @@ macro_rules! all_units {
|
||||||
}
|
}
|
||||||
$extract
|
$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
|
where
|
||||||
T: MOpTrait<DestReg = $DestReg, SrcReg = $SrcReg>,
|
T: MOpTrait<DestReg = $DestReg, SrcRegWidth = $SrcRegWidth>,
|
||||||
$TransformedMoveOp: MOpTrait<DestReg = $DestReg, SrcReg = $SrcReg>,
|
$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! {
|
all_units! {
|
||||||
@split_by_transformed_move
|
@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)*])?
|
$(#[transformed_move $($transformed_move)*])?
|
||||||
$Unit($Op),
|
$Unit($Op),
|
||||||
|
|
@ -197,29 +147,29 @@ macro_rules! all_units {
|
||||||
};
|
};
|
||||||
(
|
(
|
||||||
@split_by_transformed_move
|
@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),)*
|
$($BeforeUnit:ident($BeforeOp:ty),)*
|
||||||
#[transformed_move]
|
#[transformed_move]
|
||||||
$TransformedMove:ident($TransformedMoveOp2:ty),
|
$TransformedMove:ident($TransformedMoveOp2:ty),
|
||||||
$($AfterUnit:ident($AfterOp: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]
|
#[hdl]
|
||||||
$vis fn try_with_transformed_move_op<T>(
|
$vis fn try_with_transformed_move_op<T>(
|
||||||
this: impl ToExpr<Type = Self>,
|
this: impl ToExpr<Type = Self>,
|
||||||
new_transformed_move_op_ty: T,
|
new_transformed_move_op_ty: T,
|
||||||
connect_transformed_move_op: impl FnOnce(Expr<HdlOption<$UnitMOpEnum<$DestReg, $SrcReg, T>>>, Expr<$TransformedMoveOp>),
|
connect_transformed_move_op: impl FnOnce(Expr<HdlOption<$UnitMOpEnum<$DestReg, $SrcRegWidth, T>>>, Expr<$TransformedMoveOp>),
|
||||||
) -> Expr<HdlOption<$UnitMOpEnum<$DestReg, $SrcReg, T>>>
|
) -> Expr<HdlOption<$UnitMOpEnum<$DestReg, $SrcRegWidth, T>>>
|
||||||
where
|
where
|
||||||
T: MOpTrait<DestReg = $DestReg, SrcReg = $SrcReg>,
|
T: MOpTrait<DestReg = $DestReg, SrcRegWidth = $SrcRegWidth>,
|
||||||
$TransformedMoveOp: MOpTrait<DestReg = $DestReg, SrcReg = $SrcReg>,
|
$TransformedMoveOp: MOpTrait<DestReg = $DestReg, SrcRegWidth = $SrcRegWidth>,
|
||||||
{
|
{
|
||||||
let this = this.to_expr();
|
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]
|
#[hdl]
|
||||||
let with_transformed_move_op = wire(HdlOption[new_ty]);
|
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
|
// 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);
|
let mut connect_transformed_move_op = Some(connect_transformed_move_op);
|
||||||
#[hdl]
|
#[hdl]
|
||||||
|
|
@ -230,51 +180,12 @@ macro_rules! all_units {
|
||||||
}
|
}
|
||||||
with_transformed_move_op
|
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 _: () = {
|
const _: () = {
|
||||||
#[hdl]
|
#[hdl]
|
||||||
type $DestReg = MOpDestReg;
|
type $DestReg = MOpDestReg;
|
||||||
type $SrcReg = MOpRegNum;
|
type $SrcRegWidth = ConstUsize<{ MOpRegNum::WIDTH }>;
|
||||||
|
|
||||||
$(impl MOpInto<MOp> for $BeforeOp {
|
$(impl MOpInto<MOp> for $BeforeOp {
|
||||||
fn mop_into_ty(self) -> MOp {
|
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 {
|
const _: () = {
|
||||||
fn mop_into_ty(self) -> RenamedMOp<$DestReg, $SrcReg> {
|
#[hdl]
|
||||||
RenamedMOp[MOpTrait::dest_reg_ty(self)][MOpTrait::src_reg_ty(self)]
|
type $DestReg = ();
|
||||||
}
|
|
||||||
fn mop_into(this: Expr<Self>) -> Expr<RenamedMOp<$DestReg, $SrcReg>> {
|
|
||||||
MOpInto::<RenamedMOp<$DestReg, $SrcReg>>::mop_into_ty(this.ty()).$BeforeUnit(this)
|
|
||||||
}
|
|
||||||
})*
|
|
||||||
|
|
||||||
$(impl<$DestReg: Type, $SrcReg: Type> MOpInto<RenamedMOp<$DestReg, $SrcReg>> for $AfterOp {
|
$(impl<$SrcRegWidth: Size> MOpInto<RenamedMOp<$SrcRegWidth>> for $BeforeOp {
|
||||||
fn mop_into_ty(self) -> RenamedMOp<$DestReg, $SrcReg> {
|
fn mop_into_ty(self) -> RenamedMOp<$SrcRegWidth> {
|
||||||
RenamedMOp[MOpTrait::dest_reg_ty(self)][MOpTrait::src_reg_ty(self)]
|
RenamedMOp[MOpTrait::src_reg_width(self)]
|
||||||
}
|
}
|
||||||
fn mop_into(this: Expr<Self>) -> Expr<RenamedMOp<$DestReg, $SrcReg>> {
|
fn mop_into(this: Expr<Self>) -> Expr<RenamedMOp<$SrcRegWidth>> {
|
||||||
MOpInto::<RenamedMOp<$DestReg, $SrcReg>>::mop_into_ty(this.ty()).$AfterUnit(this)
|
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! {
|
all_units! {
|
||||||
#[hdl_unit_kind = HdlUnitKind]
|
#[hdl_unit_kind = HdlUnitKind]
|
||||||
#[unit_kind = UnitKind]
|
#[unit_kind = UnitKind]
|
||||||
#[hdl(custom_debug(sim))]
|
#[hdl]
|
||||||
pub enum UnitMOp<
|
pub enum UnitMOp<
|
||||||
DestReg: Type,
|
DestReg: Type,
|
||||||
SrcReg: Type,
|
SrcRegWidth: Size,
|
||||||
#[MOp(get_ty = |this: UnitMOp<DestReg, SrcReg, TransformedMoveOp>, new_dest_reg, new_src_reg| {
|
#[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)
|
this.TransformedMove.mapped_ty(new_dest_reg, new_src_reg_width)
|
||||||
})] TransformedMoveOp: Type
|
})] TransformedMoveOp: Type
|
||||||
> {
|
> {
|
||||||
#[create_dyn_unit_fn = |config, unit_index| alu_branch::AluBranch::new(config, unit_index).to_dyn()]
|
#[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)]
|
#[extract = alu_branch_mop]
|
||||||
AluBranch(AluBranchMOp<DestReg, SrcReg>),
|
AluBranch(AluBranchMOp<DestReg, SrcRegWidth>),
|
||||||
#[transformed_move]
|
#[transformed_move]
|
||||||
#[create_dyn_unit_fn = |config, unit_index| todo!()]
|
#[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),
|
TransformedMove(TransformedMoveOp),
|
||||||
#[create_dyn_unit_fn = |config, unit_index| todo!()]
|
#[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)]
|
#[extract = load_store_mop]
|
||||||
LoadStore(LoadStoreMOp<DestReg, SrcReg>),
|
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
|
'static + Send + Sync + std::fmt::Debug + fayalite::intern::SupportsPtrEqWithTypeId
|
||||||
{
|
{
|
||||||
type Type: BundleType;
|
type Type: BundleType;
|
||||||
|
type ExtraOut: Type;
|
||||||
|
type MOp: Type;
|
||||||
|
|
||||||
fn ty(&self) -> Self::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 unit_kind(&self) -> UnitKind;
|
||||||
|
|
||||||
|
fn extract_mop(&self, mop: Expr<RenamedMOp<DynSize>>) -> Expr<HdlOption<Self::MOp>>;
|
||||||
|
|
||||||
fn module(&self) -> Interned<Module<Self::Type>>;
|
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,
|
&self,
|
||||||
this: Expr<Self::Type>,
|
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;
|
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 {
|
impl fayalite::intern::InternedCompare for DynUnitTrait {
|
||||||
type InternedCompareKey = fayalite::intern::PtrEqWithTypeId;
|
type InternedCompareKey = fayalite::intern::PtrEqWithTypeId;
|
||||||
|
|
@ -367,34 +419,54 @@ impl fayalite::intern::InternedCompare for DynUnitTrait {
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||||
pub struct DynUnit {
|
pub struct DynUnit {
|
||||||
ty: Bundle,
|
ty: Bundle,
|
||||||
|
extra_out_ty: CanonicalType,
|
||||||
|
mop_ty: CanonicalType,
|
||||||
unit_kind: UnitKind,
|
unit_kind: UnitKind,
|
||||||
unit: Interned<DynUnitTrait>,
|
unit: Interned<DynUnitTrait>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnitTrait for DynUnit {
|
impl UnitTrait for DynUnit {
|
||||||
type Type = Bundle;
|
type Type = Bundle;
|
||||||
|
type ExtraOut = CanonicalType;
|
||||||
|
type MOp = CanonicalType;
|
||||||
|
|
||||||
fn ty(&self) -> Self::Type {
|
fn ty(&self) -> Self::Type {
|
||||||
self.ty
|
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 {
|
fn unit_kind(&self) -> UnitKind {
|
||||||
self.unit_kind
|
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>> {
|
fn module(&self) -> Interned<Module<Self::Type>> {
|
||||||
self.unit.module()
|
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)
|
self.unit.cd(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_execute(
|
fn global_state(&self, this: Expr<Self::Type>) -> Expr<GlobalState> {
|
||||||
&self,
|
self.unit.global_state(this)
|
||||||
this: Expr<Self::Type>,
|
|
||||||
) -> Expr<ExecuteToUnitInterface<PhantomConst<CpuConfig>>> {
|
|
||||||
self.unit.from_execute(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_dyn(&self) -> DynUnit {
|
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> {
|
impl<T: UnitTrait + Clone + std::hash::Hash + Eq> UnitTrait for DynUnitWrapper<T> {
|
||||||
type Type = Bundle;
|
type Type = Bundle;
|
||||||
|
type ExtraOut = CanonicalType;
|
||||||
|
type MOp = CanonicalType;
|
||||||
|
|
||||||
fn ty(&self) -> Self::Type {
|
fn ty(&self) -> Self::Type {
|
||||||
Bundle::from_canonical(self.0.ty().canonical())
|
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 {
|
fn unit_kind(&self) -> UnitKind {
|
||||||
self.0.unit_kind()
|
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>> {
|
fn module(&self) -> Interned<Module<Self::Type>> {
|
||||||
self.0.module().canonical().intern_sized()
|
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))
|
self.0.cd(Expr::from_bundle(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_execute(
|
fn global_state(&self, this: Expr<Self::Type>) -> Expr<GlobalState> {
|
||||||
&self,
|
self.0.global_state(Expr::from_bundle(this))
|
||||||
this: Expr<Self::Type>,
|
|
||||||
) -> Expr<ExecuteToUnitInterface<PhantomConst<CpuConfig>>> {
|
|
||||||
self.0.from_execute(Expr::from_bundle(this))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_dyn(&self) -> DynUnit {
|
fn to_dyn(&self) -> DynUnit {
|
||||||
let unit = self.intern();
|
let unit = self.intern();
|
||||||
DynUnit {
|
DynUnit {
|
||||||
ty: unit.ty(),
|
ty: unit.ty(),
|
||||||
|
extra_out_ty: unit.extra_out_ty(),
|
||||||
|
mop_ty: unit.mop_ty(),
|
||||||
unit_kind: unit.unit_kind(),
|
unit_kind: unit.unit_kind(),
|
||||||
unit: Interned::cast_unchecked(unit, |v: &Self| -> &DynUnitTrait { v }),
|
unit: Interned::cast_unchecked(unit, |v: &Self| -> &DynUnitTrait { v }),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,35 +2,30 @@
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{CpuConfig, PhantomConstCpuConfig},
|
config::{CpuConfig, PRegNumWidth},
|
||||||
instruction::{
|
instruction::{
|
||||||
AddSubMOp, AluBranchMOp, AluCommonMOp, BranchMOp, COMMON_MOP_SRC_LEN, CommonMOp,
|
AddSubMOp, AluBranchMOp, AluCommonMOp, CommonMOp, LogicalMOp, OutputIntegerMode,
|
||||||
CommonMOpDefaultImm, CompareMOp, ConditionMode, LogicalFlagsMOp, LogicalMOp,
|
RenamedMOp, COMMON_MOP_SRC_LEN,
|
||||||
OutputIntegerMode, PRegNum, ReadSpecialMOp, ShiftRotateMOp,
|
|
||||||
},
|
},
|
||||||
next_pc::CallStackOp,
|
register::{FlagsMode, PRegFlagsPowerISA, PRegFlagsX86, PRegValue},
|
||||||
register::{
|
unit::{
|
||||||
FlagsMode, PRegFlags, PRegFlagsPowerISA, PRegFlagsPowerISAView, PRegFlagsViewTrait,
|
unit_base::{unit_base, ExecuteEnd, ExecuteStart},
|
||||||
PRegFlagsX86, PRegFlagsX86View, PRegValue, ViewUnused,
|
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};
|
use std::{collections::HashMap, ops::RangeTo};
|
||||||
|
|
||||||
#[hdl]
|
#[hdl]
|
||||||
fn add_sub<C: PhantomConstCpuConfig, SrcCount: KnownSize>(
|
fn add_sub<SrcCount: KnownSize>(
|
||||||
global_state: Expr<GlobalState>,
|
mop: Expr<AddSubMOp<(), DynSize, SrcCount>>,
|
||||||
pc: Expr<UInt<64>>,
|
pc: Expr<UInt<64>>,
|
||||||
mop: Expr<AddSubMOp<PRegNum<C>, PRegNum<C>, SrcCount>>,
|
flags_mode: Expr<FlagsMode>,
|
||||||
src_values: Expr<Array<TraceAsString<PRegValue>, { COMMON_MOP_SRC_LEN }>>,
|
src_values: Expr<Array<PRegValue, { COMMON_MOP_SRC_LEN }>>,
|
||||||
) -> Expr<TraceAsString<PRegValue>> {
|
) -> Expr<UnitResultCompleted<()>> {
|
||||||
#[hdl]
|
|
||||||
let GlobalState { flags_mode } = global_state;
|
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let AddSubMOp::<_, _, _> {
|
let AddSubMOp::<_, _, _> {
|
||||||
alu_common,
|
alu_common,
|
||||||
|
|
@ -40,11 +35,11 @@ fn add_sub<C: PhantomConstCpuConfig, SrcCount: KnownSize>(
|
||||||
add_pc,
|
add_pc,
|
||||||
} = mop;
|
} = mop;
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let AluCommonMOp::<_, _, _, _> {
|
let AluCommonMOp::<_, _, _> {
|
||||||
common,
|
common,
|
||||||
output_integer_mode,
|
output_integer_mode,
|
||||||
} = alu_common;
|
} = 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]
|
#[hdl]
|
||||||
let carry_in_before_inversion = wire();
|
let carry_in_before_inversion = wire();
|
||||||
connect(carry_in_before_inversion, false);
|
connect(carry_in_before_inversion, false);
|
||||||
|
|
@ -58,13 +53,13 @@ fn add_sub<C: PhantomConstCpuConfig, SrcCount: KnownSize>(
|
||||||
FlagsMode::PowerISA(_) => {
|
FlagsMode::PowerISA(_) => {
|
||||||
connect(
|
connect(
|
||||||
carry_in_before_inversion,
|
carry_in_before_inversion,
|
||||||
PRegFlagsPowerISA::view(src_values[1].flags).xer_ca,
|
PRegFlagsPowerISA::xer_ca(src_values[1].flags),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
FlagsMode::X86(_) => {
|
FlagsMode::X86(_) => {
|
||||||
connect(
|
connect(
|
||||||
carry_in_before_inversion,
|
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]
|
#[hdl]
|
||||||
match flags_mode {
|
match flags_mode {
|
||||||
FlagsMode::PowerISA(_) => {
|
FlagsMode::PowerISA(_) => {
|
||||||
connect(
|
PRegFlagsPowerISA::clear_unused(flags);
|
||||||
flags,
|
connect(PRegFlagsPowerISA::xer_ca(flags), pwr_ca);
|
||||||
PRegFlagsPowerISA::from_view(PRegFlagsPowerISAView {
|
connect(PRegFlagsPowerISA::xer_ca32(flags), pwr_ca32);
|
||||||
unused: ViewUnused::splat(false.to_expr()),
|
connect(PRegFlagsPowerISA::xer_ov(flags), pwr_ov);
|
||||||
xer_ca: pwr_ca,
|
connect(PRegFlagsPowerISA::xer_ov32(flags), pwr_ov32);
|
||||||
xer_ca32: pwr_ca32,
|
connect(PRegFlagsPowerISA::cr_lt(flags), pwr_cr_lt);
|
||||||
xer_ov: pwr_ov,
|
connect(PRegFlagsPowerISA::cr_gt(flags), pwr_cr_gt);
|
||||||
xer_ov32: pwr_ov32,
|
connect(PRegFlagsPowerISA::cr_eq(flags), pwr_cr_eq);
|
||||||
so: pwr_so,
|
connect(PRegFlagsPowerISA::so(flags), pwr_so);
|
||||||
cr_lt: pwr_cr_lt,
|
|
||||||
cr_gt: pwr_cr_gt,
|
|
||||||
cr_eq: pwr_cr_eq,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
FlagsMode::X86(_) => {
|
FlagsMode::X86(_) => {
|
||||||
connect(
|
PRegFlagsX86::clear_unused(flags);
|
||||||
flags,
|
connect(PRegFlagsX86::cf(flags), x86_cf);
|
||||||
PRegFlagsX86::from_view(PRegFlagsX86View {
|
connect(PRegFlagsX86::af(flags), x86_af);
|
||||||
unused: ViewUnused::splat(false.to_expr()),
|
connect(PRegFlagsX86::of(flags), x86_of);
|
||||||
cf: x86_cf,
|
connect(PRegFlagsX86::sf(flags), x86_sf);
|
||||||
zf: x86_zf,
|
connect(PRegFlagsX86::pf(flags), x86_pf);
|
||||||
sf: x86_sf,
|
connect(PRegFlagsX86::zf(flags), x86_zf);
|
||||||
of: x86_of,
|
|
||||||
af: x86_af,
|
// this insn doesn't write DF, so it's output isn't used for reading DF
|
||||||
pf: x86_pf,
|
connect(PRegFlagsX86::df(flags), false);
|
||||||
// this insn doesn't write DF, so it's output isn't used for reading DF
|
|
||||||
df: false.to_expr(),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let retval = #[hdl]
|
#[hdl]
|
||||||
PRegValue { int_fp, flags };
|
UnitResultCompleted::<_> {
|
||||||
retval.to_trace_as_string()
|
value: #[hdl]
|
||||||
|
PRegValue { int_fp, flags },
|
||||||
|
extra_out: (),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[hdl]
|
#[hdl]
|
||||||
fn logical_flags<C: PhantomConstCpuConfig>(
|
fn logical(
|
||||||
global_state: Expr<GlobalState>,
|
mop: Expr<LogicalMOp<(), DynSize>>,
|
||||||
pc: Expr<UInt<64>>,
|
flags_mode: Expr<FlagsMode>,
|
||||||
mop: Expr<LogicalFlagsMOp<PRegNum<C>, PRegNum<C>>>,
|
src_values: Expr<Array<PRegValue, { COMMON_MOP_SRC_LEN }>>,
|
||||||
src_values: Expr<Array<TraceAsString<PRegValue>, { COMMON_MOP_SRC_LEN }>>,
|
) -> Expr<UnitResultCompleted<()>> {
|
||||||
) -> Expr<TraceAsString<PRegValue>> {
|
|
||||||
#[hdl]
|
|
||||||
let GlobalState { flags_mode } = global_state;
|
|
||||||
// TODO: finish
|
// 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]
|
#[hdl]
|
||||||
let GlobalState { flags_mode } = global_state;
|
UnitResultCompleted::<_> {
|
||||||
// TODO: finish
|
value: PRegValue::zeroed(),
|
||||||
PRegValue::zeroed().to_trace_as_string()
|
extra_out: (),
|
||||||
}
|
|
||||||
|
|
||||||
#[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);
|
|
||||||
}
|
}
|
||||||
#[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]
|
#[hdl_module]
|
||||||
pub fn alu_branch(config: PhantomConst<CpuConfig>, unit_index: usize) {
|
pub fn alu_branch(config: PhantomConst<CpuConfig>, unit_index: usize) {
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let from_execute: ExecuteToUnitInterface<PhantomConst<CpuConfig>> =
|
let cd: ClockDomain = m.input();
|
||||||
m.input(ExecuteToUnitInterface[config]);
|
#[hdl]
|
||||||
|
let unit_to_reg_alloc: UnitToRegAlloc<
|
||||||
assert_eq!(config.get().units[unit_index].kind, UnitKind::AluBranch);
|
PhantomConst<CpuConfig>,
|
||||||
|
AluBranchMOp<(), DynSize>,
|
||||||
|
(),
|
||||||
|
> = m.output(UnitToRegAlloc[config][AluBranchMOp[()][PRegNumWidth[config]]][()]);
|
||||||
|
#[hdl]
|
||||||
|
let global_state: GlobalState = m.input();
|
||||||
|
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let ExecuteToUnitInterface::<_> {
|
let unit_base = instance(unit_base(
|
||||||
global_state,
|
config,
|
||||||
enqueue,
|
unit_index,
|
||||||
inputs_ready,
|
Expr::ty(unit_to_reg_alloc).input.data.HdlSome.mop,
|
||||||
is_no_longer_speculative: _, // we don't care about being speculative for these instructions
|
(),
|
||||||
cant_cause_cancel,
|
));
|
||||||
output_ready,
|
connect(unit_to_reg_alloc, unit_base.unit_to_reg_alloc);
|
||||||
finish_cause_cancel,
|
connect(unit_base.cd, cd);
|
||||||
unit_outputs_ready: _,
|
connect(unit_base.execute_start.ready, true);
|
||||||
cancel_all,
|
connect(
|
||||||
config: _,
|
unit_base.execute_end,
|
||||||
} = from_execute;
|
Expr::ty(unit_base.execute_end).HdlNone(),
|
||||||
// we ignore enqueues since we don't need to track order for these instructions
|
);
|
||||||
connect(enqueue.ready, true);
|
|
||||||
#[hdl]
|
#[hdl]
|
||||||
if let HdlSome(inputs_ready) = inputs_ready {
|
if let HdlSome(execute_start) = ReadyValid::firing_data(unit_base.execute_start) {
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let UnitInputsReady::<_> {
|
let ExecuteStart::<_, _> { insn, src_values } = execute_start;
|
||||||
mop: mop_instance,
|
|
||||||
src_values,
|
|
||||||
config: _,
|
|
||||||
} = inputs_ready;
|
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let fallthrough_pc: UInt<64> = wire();
|
match insn.mop {
|
||||||
connect_any(fallthrough_pc, mop_instance.pc + mop_instance.size_in_bytes);
|
AluBranchMOp::<_, _>::AddSub(mop) => connect(
|
||||||
|
unit_base.execute_end,
|
||||||
#[hdl]
|
HdlSome(
|
||||||
match *mop_instance.mop {
|
|
||||||
RenamedMOp::<_>::AluBranch(mop) => {
|
|
||||||
#[hdl]
|
|
||||||
let dest_value = wire();
|
|
||||||
|
|
||||||
#[hdl]
|
|
||||||
let predictor_op = wire(NextPcPredictorOp[config]);
|
|
||||||
connect(
|
|
||||||
predictor_op,
|
|
||||||
#[hdl]
|
#[hdl]
|
||||||
NextPcPredictorOp::<_> {
|
ExecuteEnd::<_, _> {
|
||||||
call_stack_op: CallStackOp.None(),
|
unit_output: #[hdl]
|
||||||
cond_br_taken: HdlNone(),
|
UnitOutput::<_, _> {
|
||||||
config,
|
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]
|
AluBranchMOp::<_, _>::AddSubI(mop) => connect(
|
||||||
let caused_cancel = wire(HdlOption[UnitCausedCancel[config]]);
|
unit_base.execute_end,
|
||||||
connect(caused_cancel, caused_cancel.ty().HdlNone());
|
HdlSome(
|
||||||
|
#[hdl]
|
||||||
#[hdl]
|
ExecuteEnd::<_, _> {
|
||||||
match mop {
|
unit_output: #[hdl]
|
||||||
AluBranchMOp::<_, _>::AddSub(mop) => {
|
UnitOutput::<_, _> {
|
||||||
connect(
|
dest: insn.dest,
|
||||||
dest_value,
|
retire_queue_index: insn.retire_queue_index,
|
||||||
add_sub(global_state, mop_instance.pc, mop, src_values),
|
result: UnitResult[()].Completed(add_sub(
|
||||||
);
|
mop,
|
||||||
}
|
insn.pc,
|
||||||
AluBranchMOp::<_, _>::AddSubI(mop) => {
|
global_state.flags_mode,
|
||||||
connect(
|
src_values,
|
||||||
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,
|
|
||||||
},
|
},
|
||||||
),
|
},
|
||||||
);
|
),
|
||||||
connect(
|
),
|
||||||
finish_cause_cancel,
|
AluBranchMOp::<_, _>::Logical(mop) => connect(
|
||||||
HdlSome(
|
unit_base.execute_end,
|
||||||
#[hdl]
|
HdlSome(
|
||||||
UnitFinishCauseCancel::<_> {
|
#[hdl]
|
||||||
id: mop_instance.id,
|
ExecuteEnd::<_, _> {
|
||||||
caused_cancel,
|
unit_output: #[hdl]
|
||||||
config,
|
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)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
|
|
@ -696,28 +351,46 @@ impl AluBranch {
|
||||||
|
|
||||||
impl UnitTrait for AluBranch {
|
impl UnitTrait for AluBranch {
|
||||||
type Type = alu_branch;
|
type Type = alu_branch;
|
||||||
|
type ExtraOut = ();
|
||||||
|
type MOp = AluBranchMOp<(), DynSize>;
|
||||||
|
|
||||||
fn ty(&self) -> Self::Type {
|
fn ty(&self) -> Self::Type {
|
||||||
self.module.io_ty()
|
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 {
|
fn unit_kind(&self) -> UnitKind {
|
||||||
UnitKind::AluBranch
|
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>> {
|
fn module(&self) -> Interned<Module<Self::Type>> {
|
||||||
self.module
|
self.module
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cd(&self, this: Expr<Self::Type>) -> Option<Expr<ClockDomain>> {
|
fn unit_to_reg_alloc(
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_execute(
|
|
||||||
&self,
|
&self,
|
||||||
this: Expr<Self::Type>,
|
this: Expr<Self::Type>,
|
||||||
) -> Expr<ExecuteToUnitInterface<PhantomConst<CpuConfig>>> {
|
) -> Expr<UnitToRegAlloc<PhantomConst<CpuConfig>, Self::MOp, Self::ExtraOut>> {
|
||||||
this.from_execute
|
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 {
|
fn to_dyn(&self) -> DynUnit {
|
||||||
|
|
|
||||||
666
crates/cpu/src/unit/unit_base.rs
Normal file
666
crates/cpu/src/unit/unit_base.rs
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,18 +1,7 @@
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
// See Notices.txt for copyright information
|
// 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 array_vec;
|
||||||
pub mod tree_reduce;
|
|
||||||
|
|
||||||
pub(crate) const fn range_u32_len(range: &std::ops::Range<u32>) -> usize {
|
pub(crate) const fn range_u32_len(range: &std::ops::Range<u32>) -> usize {
|
||||||
let retval = range.end.saturating_sub(range.start);
|
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(crate) const fn range_intersection(
|
||||||
pub trait Rotate<Amount> {
|
a: &std::ops::Range<u32>,
|
||||||
type Output;
|
b: &std::ops::Range<u32>,
|
||||||
/// like [`usize::rotate_left`] or [`<[T]>::rotate_left`](slice::rotate_left) depending on `Self` -- note that in lsb0 those rotate in opposite directions
|
) -> Option<std::ops::Range<u32>> {
|
||||||
fn rotate_left(&self, amount: Amount) -> Self::Output;
|
let start = if a.start > b.start { a.start } else { b.start };
|
||||||
/// like [`usize::rotate_right`] or [`<[T]>::rotate_right`](slice::rotate_right) depending on `Self` -- note that in lsb0 those rotate in opposite directions
|
let end = if a.end < b.end { a.end } else { b.end };
|
||||||
fn rotate_right(&self, amount: Amount) -> Self::Output;
|
if start < end {
|
||||||
}
|
Some(start..end)
|
||||||
|
} else {
|
||||||
impl<VSz: Size> Rotate<usize> for Expr<UIntType<VSz>> {
|
None
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,150 @@
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
use fayalite::{expr::ops::ExprIndex, int::UIntInRangeInclusiveType, prelude::*};
|
use fayalite::{
|
||||||
use std::fmt;
|
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)]
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||||
pub struct ArrayVecFullError<V, I: Iterator> {
|
pub struct Length<Max: Size> {
|
||||||
pub value: V,
|
ty: UInt,
|
||||||
pub rest: std::iter::Chain<std::iter::Once<I::Item>, I>,
|
_phantom: PhantomData<Max>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V, I: Iterator> fmt::Display for ArrayVecFullError<V, I> {
|
impl<Max: Size> Length<Max> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
pub fn new(max: Max::SizeType) -> Self {
|
||||||
write!(f, "ArrayVec is full")
|
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
|
impl<Max: Size, DestWidth: Size> ExprCastTo<UIntType<DestWidth>> for Length<Max> {
|
||||||
for ArrayVecFullError<V, I>
|
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]
|
#[allow(non_upper_case_globals)]
|
||||||
pub type Length<Max: Size> = UIntInRangeInclusiveType<ConstUsize<0>, Max>;
|
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
|
/// like [`std::vec::Vec`], except with a [`Expr`] for [`len()`][`Self::len()`] and a fixed capacity
|
||||||
#[hdl]
|
#[hdl]
|
||||||
|
|
@ -37,55 +159,7 @@ impl<T: Type, N: Size> ArrayVec<T, N> {
|
||||||
#[hdl]
|
#[hdl]
|
||||||
ArrayVec {
|
ArrayVec {
|
||||||
elements: self.elements.uninit(),
|
elements: self.elements.uninit(),
|
||||||
len: 0u8.cast_to(self.len),
|
len: self.len.zero(),
|
||||||
}
|
|
||||||
}
|
|
||||||
#[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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn element(self) -> T {
|
pub fn element(self) -> T {
|
||||||
|
|
@ -105,8 +179,8 @@ impl<T: Type, N: Size> ArrayVec<T, N> {
|
||||||
let elements = elements.to_expr();
|
let elements = elements.to_expr();
|
||||||
let len = len.to_expr();
|
let len = len.to_expr();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Length[N::from_usize(elements.ty().len())],
|
Length::new(N::from_usize(Expr::ty(elements).len())),
|
||||||
len.ty(),
|
Expr::ty(len),
|
||||||
"len type mismatch",
|
"len type mismatch",
|
||||||
);
|
);
|
||||||
#[hdl]
|
#[hdl]
|
||||||
|
|
@ -118,12 +192,9 @@ impl<T: Type, N: Size> ArrayVec<T, N> {
|
||||||
pub fn len(this: impl ToExpr<Type = Self>) -> Expr<Length<N>> {
|
pub fn len(this: impl ToExpr<Type = Self>) -> Expr<Length<N>> {
|
||||||
this.to_expr().len
|
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> {
|
pub fn is_empty(this: impl ToExpr<Type = Self>) -> Expr<Bool> {
|
||||||
let len = Self::len(this);
|
let len = Self::len(this);
|
||||||
len.cmp_eq(0u8)
|
len.cmp_eq(Expr::ty(len).zero())
|
||||||
}
|
}
|
||||||
pub fn capacity(self) -> usize {
|
pub fn capacity(self) -> usize {
|
||||||
self.elements.len()
|
self.elements.len()
|
||||||
|
|
@ -139,77 +210,11 @@ impl<T: Type, N: Size> ArrayVec<T, N> {
|
||||||
let this = this.to_expr();
|
let this = this.to_expr();
|
||||||
for (index, element) in this.elements.into_iter().enumerate() {
|
for (index, element) in this.elements.into_iter().enumerate() {
|
||||||
#[hdl]
|
#[hdl]
|
||||||
if index.cmp_lt(this.len) {
|
if index.cmp_lt(Length::as_uint(this.len)) {
|
||||||
f(index, element);
|
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> {
|
pub fn mapped_ty<U: Type>(self, new_element_ty: U) -> ArrayVec<U, N> {
|
||||||
ArrayVec {
|
ArrayVec {
|
||||||
elements: ArrayType[new_element_ty][N::from_usize(self.elements.len())],
|
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>> {
|
) -> Expr<ArrayVec<U, N>> {
|
||||||
let this = this.to_expr();
|
let this = this.to_expr();
|
||||||
#[hdl]
|
#[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);
|
connect(mapped_array_vec.len, this.len);
|
||||||
Self::for_each(this, |index, element| {
|
Self::for_each(this, |index, element| {
|
||||||
connect(mapped_array_vec[index], f(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
|
mapped_array_vec
|
||||||
}
|
}
|
||||||
#[hdl]
|
#[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>> {
|
pub fn as_array_of_options(this: impl ToExpr<Type = Self>) -> Expr<ArrayType<HdlOption<T>, N>> {
|
||||||
let this = this.to_expr();
|
let this = this.to_expr();
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let array_vec_as_array_of_options =
|
let array_vec_as_array_of_options = wire(
|
||||||
wire(ArrayType[HdlOption[this.ty().element()]][N::from_usize(this.ty().capacity())]);
|
ArrayType[HdlOption[Expr::ty(this).element()]]
|
||||||
|
[N::from_usize(Expr::ty(this).capacity())],
|
||||||
|
);
|
||||||
for element in array_vec_as_array_of_options {
|
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| {
|
Self::for_each(this, |index, element| {
|
||||||
connect(array_vec_as_array_of_options[index], HdlSome(element))
|
connect(array_vec_as_array_of_options[index], HdlSome(element))
|
||||||
});
|
});
|
||||||
array_vec_as_array_of_options
|
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>
|
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]
|
#[hdl]
|
||||||
pub fn from_opt_sim(
|
pub fn firing_len(this: impl ToExpr<Type = Self>) -> Expr<Length<N>> {
|
||||||
opt: impl ToSimValue<Type = HdlOption<T>>,
|
let this = this.to_expr();
|
||||||
uninit_element: impl ToSimValueWithType<T>,
|
assert_eq!(Expr::ty(this).data.len_ty(), Expr::ty(this).ready);
|
||||||
) -> SimValue<Self> {
|
#[hdl]
|
||||||
let opt = opt.into_sim_value();
|
let firing_len = wire(Expr::ty(this).data.len);
|
||||||
let ty = ArrayVec[opt.ty().HdlSome][ConstUsize];
|
connect(firing_len, this.data.len);
|
||||||
#[hdl(sim)]
|
#[hdl]
|
||||||
match opt {
|
if this.data.len.cmp_gt(this.ready) {
|
||||||
HdlSome(v) => ty.new_full_sim([v]),
|
connect(firing_len, this.ready);
|
||||||
HdlNone => ty.new_sim(uninit_element),
|
|
||||||
}
|
}
|
||||||
|
firing_len
|
||||||
}
|
}
|
||||||
#[hdl]
|
#[hdl]
|
||||||
pub fn into_opt_sim(this: impl ToSimValue<Type = Self>) -> SimValue<HdlOption<T>> {
|
pub fn firing_data(this: impl ToExpr<Type = Self>) -> Expr<ArrayVec<T, N>> {
|
||||||
let this = this.into_sim_value();
|
let this = this.to_expr();
|
||||||
#[hdl(sim)]
|
#[hdl]
|
||||||
let Self { elements, len } = this;
|
let firing_data = wire(Expr::ty(this).data);
|
||||||
let [element] = SimValue::into_value(elements);
|
connect(firing_data, this.data);
|
||||||
let ty = HdlOption[element.ty()];
|
connect(firing_data.len, Self::firing_len(this));
|
||||||
if *len == 0 {
|
firing_data
|
||||||
#[hdl(sim)]
|
|
||||||
ty.HdlNone()
|
|
||||||
} else {
|
|
||||||
#[hdl(sim)]
|
|
||||||
ty.HdlSome(element)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
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
|
|
@ -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!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
@ -1,17 +1,20 @@
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
#![cfg(todo)]
|
#![cfg(todo)]
|
||||||
|
|
||||||
use cpu::{
|
use cpu::{
|
||||||
config::{CpuConfig, UnitConfig},
|
config::{CpuConfig, UnitConfig},
|
||||||
instruction::{AddSubMOp, LogicalMOp, Lut4, MOp, MOpDestReg, MOpRegNum, OutputIntegerMode},
|
instruction::{AddSubMOp, LogicalMOp, MOp, MOpDestReg, MOpRegNum, OutputIntegerMode},
|
||||||
reg_alloc::{FetchedDecodedMOp, reg_alloc},
|
reg_alloc::{reg_alloc, FetchedDecodedMOp},
|
||||||
register::{FlagsMode, PRegFlagsPowerISA},
|
register::{FlagsMode, PRegFlagsPowerISA},
|
||||||
unit::{GlobalState, UnitKind},
|
unit::{GlobalState, UnitKind},
|
||||||
};
|
};
|
||||||
use fayalite::{
|
use fayalite::{
|
||||||
|
assert_export_firrtl,
|
||||||
|
firrtl::ExportOptions,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
sim::{Simulation, time::SimDuration, vcd::VcdWriterDecls},
|
sim::{time::SimDuration, vcd::VcdWriterDecls, Simulation},
|
||||||
util::RcWriter,
|
util::RcWriter,
|
||||||
};
|
};
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|
@ -60,7 +63,7 @@ fn test_reg_alloc() {
|
||||||
[HdlSome(()), HdlNone()],
|
[HdlSome(()), HdlNone()],
|
||||||
},
|
},
|
||||||
[0u8; 2],
|
[0u8; 2],
|
||||||
0x12345678u32.cast_to_static::<SInt<_>>(),
|
0x12345678u32.cast_to_static(),
|
||||||
OutputIntegerMode.DupLow32(),
|
OutputIntegerMode.DupLow32(),
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
|
@ -82,7 +85,7 @@ fn test_reg_alloc() {
|
||||||
[HdlSome(()), HdlNone()],
|
[HdlSome(()), HdlNone()],
|
||||||
},
|
},
|
||||||
[1u8, 0, 0],
|
[1u8, 0, 0],
|
||||||
1.cast_to_static::<SInt<_>>(),
|
1.cast_to_static(),
|
||||||
OutputIntegerMode.Full64(),
|
OutputIntegerMode.Full64(),
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
|
@ -100,9 +103,9 @@ fn test_reg_alloc() {
|
||||||
flag_regs: [HdlNone(), HdlSome(())],
|
flag_regs: [HdlNone(), HdlSome(())],
|
||||||
},
|
},
|
||||||
[2u8, 4u8],
|
[2u8, 4u8],
|
||||||
0.cast_to_static::<SInt<_>>(),
|
0.cast_to_static(),
|
||||||
OutputIntegerMode.Full64(),
|
OutputIntegerMode.Full64(),
|
||||||
Lut4::from_fn(|a, b| a ^ b),
|
0b0110_hdl_u4,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
let insns = insns_init.into_iter().chain(insns_loop.into_iter().cycle());
|
let insns = insns_init.into_iter().chain(insns_loop.into_iter().cycle());
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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,
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
@ -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,
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
@ -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(),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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(),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
@ -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),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
@ -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!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
20
crates/name_mangling_serde/Cargo.toml
Normal file
20
crates/name_mangling_serde/Cargo.toml
Normal 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)'] }
|
||||||
1
crates/name_mangling_serde/LICENSE.md
Symbolic link
1
crates/name_mangling_serde/LICENSE.md
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../LICENSE.md
|
||||||
1
crates/name_mangling_serde/Notices.txt
Symbolic link
1
crates/name_mangling_serde/Notices.txt
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../Notices.txt
|
||||||
470
crates/name_mangling_serde/src/lib.rs
Normal file
470
crates/name_mangling_serde/src/lib.rs
Normal 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",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -31,7 +31,6 @@ function check_file()
|
||||||
POUND_HEADER=('^"# SPDX-License-Identifier: LGPL-3.0-or-later"$' '^"# See Notices.txt for copyright information"$')
|
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"$')
|
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"$')
|
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()
|
function main()
|
||||||
{
|
{
|
||||||
|
|
@ -46,15 +45,12 @@ function main()
|
||||||
*/LICENSE.md|*/Notices.txt)
|
*/LICENSE.md|*/Notices.txt)
|
||||||
# copyright file
|
# 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
|
# file that can't contain copyright header
|
||||||
;;
|
;;
|
||||||
/.forgejo/workflows/*.yml|*/.gitignore|*/.gitattributes|*.toml)
|
/.forgejo/workflows/*.yml|*/.gitignore|*.toml)
|
||||||
check_file "$file" "${POUND_HEADER[@]}"
|
check_file "$file" "${POUND_HEADER[@]}"
|
||||||
;;
|
;;
|
||||||
*.mermaid)
|
|
||||||
check_file "$file" "${MERMAID_HEADER[@]}"
|
|
||||||
;;
|
|
||||||
*.md)
|
*.md)
|
||||||
check_file "$file" "${MD_HEADER[@]}"
|
check_file "$file" "${MD_HEADER[@]}"
|
||||||
;;
|
;;
|
||||||
|
|
@ -71,4 +67,4 @@ function main()
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
main
|
main
|
||||||
Loading…
Add table
Add a link
Reference in a new issue