Compare commits

..

27 commits

Author SHA1 Message Date
3b5104c8fa
fix vcd to match test output 2025-12-16 23:25:54 -08:00
2de4a67360
add link to other NLnet task and note lack of optimization 2025-12-16 23:10:07 -08:00
d5a7d9dd9e
next_pc works afaict 2025-12-16 23:06:32 -08:00
59da0aec06
WIP fixing bugs 2025-12-16 02:32:19 -08:00
5e1238b5c7
replace tests/next_pc's demo program with a simple expression parser as a better demo 2025-12-15 21:40:11 -08:00
bc9a3a5ce7
add missing copyright header and check-copyright.sh support for .mermaid 2025-12-15 03:00:37 -08:00
d42f010cda
WIP fixing bugs 2025-12-15 02:48:40 -08:00
84e4fde512
fix queueing errors 2025-12-15 00:47:53 -08:00
8ab63f3c6a
update fayalite 2025-12-14 21:05:59 -08:00
f39f40ce1f
fix next_pc::Queue and add test 2025-12-14 20:39:15 -08:00
cbd52c60a8
WIP: next_pc test fails 2025-12-14 01:26:18 -08:00
c87a1b8e1e
wrote out all of next_pc and tests/next_pc 2025-12-13 22:42:34 -08:00
cfd04469ce
WIP linking next_pc stages together 2025-12-10 20:31:04 -08:00
231f5e72ec
WIP: completed stages of next-pc logic, still need to combine them into a pipeline 2025-12-10 20:31:04 -08:00
033d5d4f34
WIP adding next_pc: add call stack 2025-12-10 20:31:04 -08:00
7a77c02cda
WIP adding next_pc: added mock_fetch_decode_pipe 2025-12-10 20:31:04 -08:00
61d52bd028
add Serialize/Deserialize impls for CpuConfig 2025-12-10 20:31:04 -08:00
554238c544
update for new fayalite 2025-12-10 20:31:04 -08:00
24d6537ffe
update Fayalite to get new features and bug fixes 2025-12-10 20:31:04 -08:00
c30bd0737f
add readme 2025-10-24 16:36:31 -07:00
291ad59b02
update CI to use new fayalite-deps container 2025-10-24 02:54:03 -07:00
5e8bc3e580
upgrade to Fayalite edcc5927a5f9ebca6
as part of this I switched to using Fayalite's UIntInRangeInclusive instead of using a custom Length struct
2025-10-24 02:53:35 -07:00
0e7a518bd0
switch to use server's new actions org 2025-10-10 00:03:53 -07:00
00ddd602c5
format code 2025-08-24 19:08:58 -07:00
b969249f5f
switch to edition 2024 2025-08-24 19:07:53 -07:00
aa50ae543e
upgrade to rust 1.89.0 2025-08-24 19:06:01 -07:00
7de7d5435a
make forgejo actions actually run 2025-08-24 18:50:33 -07:00
32 changed files with 309624 additions and 3572 deletions

View file

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

View file

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

182
Cargo.lock generated
View file

@ -1,18 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "ahash"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
version = 4
[[package]]
name = "allocator-api2"
@ -93,6 +81,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bitflags"
version = "2.6.0"
@ -172,6 +166,15 @@ dependencies = [
"strsim",
]
[[package]]
name = "clap_complete"
version = "4.5.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2348487adcd4631696ced64ccdb40d38ac4d31cae7f2eec8817fcea1b9d1c43c"
dependencies = [
"clap",
]
[[package]]
name = "clap_derive"
version = "4.5.18"
@ -207,8 +210,8 @@ name = "cpu"
version = "0.1.0"
dependencies = [
"fayalite",
"name_mangling_serde",
"serde",
"simple-mermaid",
]
[[package]]
@ -302,20 +305,22 @@ checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
[[package]]
name = "fayalite"
version = "0.3.0"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#d453755bb2cd0b6f2340f3e49058d29a2ee279e8"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#c97b44d9d646a4aa64fcc046538fc2354bb708ee"
dependencies = [
"base64",
"bitvec",
"blake3",
"clap",
"clap_complete",
"ctor",
"eyre",
"fayalite-proc-macros",
"fayalite-visit-gen",
"hashbrown",
"hashbrown 0.15.5",
"jobslot",
"num-bigint",
"num-traits",
"os_pipe",
"ordered-float",
"petgraph",
"serde",
"serde_json",
@ -327,7 +332,7 @@ dependencies = [
[[package]]
name = "fayalite-proc-macros"
version = "0.3.0"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#d453755bb2cd0b6f2340f3e49058d29a2ee279e8"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#c97b44d9d646a4aa64fcc046538fc2354bb708ee"
dependencies = [
"fayalite-proc-macros-impl",
]
@ -335,7 +340,7 @@ dependencies = [
[[package]]
name = "fayalite-proc-macros-impl"
version = "0.3.0"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#d453755bb2cd0b6f2340f3e49058d29a2ee279e8"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#c97b44d9d646a4aa64fcc046538fc2354bb708ee"
dependencies = [
"base16ct",
"num-bigint",
@ -350,7 +355,7 @@ dependencies = [
[[package]]
name = "fayalite-visit-gen"
version = "0.3.0"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#d453755bb2cd0b6f2340f3e49058d29a2ee279e8"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#c97b44d9d646a4aa64fcc046538fc2354bb708ee"
dependencies = [
"indexmap",
"prettyplease",
@ -368,6 +373,12 @@ version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "funty"
version = "2.0.0"
@ -386,13 +397,14 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.2.15"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"libc",
"wasi",
"r-efi",
"wasip2",
]
[[package]]
@ -400,9 +412,16 @@ name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"ahash",
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]]
@ -433,7 +452,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5"
dependencies = [
"equivalent",
"hashbrown",
"hashbrown 0.14.5",
"serde",
]
@ -451,16 +470,16 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "jobslot"
version = "0.2.19"
version = "0.2.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe10868679d7a24c2c67d862d0e64a342ce9aef7cdde9ce8019bd35d353d458d"
checksum = "58715c67c327da7f1558708348d68c207fd54900c4ae0529e29305d04d795b8c"
dependencies = [
"cfg-if",
"derive_destructure2",
"getrandom",
"libc",
"scopeguard",
"windows-sys 0.59.0",
"windows-sys 0.61.2",
]
[[package]]
@ -481,14 +500,6 @@ version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "name_mangling_serde"
version = "0.1.0"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "num-bigint"
version = "0.4.6"
@ -524,22 +535,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "os_pipe"
version = "1.2.1"
name = "ordered-float"
version = "5.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982"
checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d"
dependencies = [
"libc",
"windows-sys 0.59.0",
"num-traits",
"rand",
"serde",
]
[[package]]
name = "petgraph"
version = "0.6.5"
source = "git+https://github.com/programmerjake/petgraph.git?rev=258ea8071209a924b73fe96f9f87a3b7b45cbc9f#258ea8071209a924b73fe96f9f87a3b7b45cbc9f"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
dependencies = [
"fixedbitset",
"hashbrown 0.15.5",
"indexmap",
"serde",
]
[[package]]
@ -570,12 +585,37 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "radium"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"rand_core",
"serde",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"serde",
]
[[package]]
name = "rustix"
version = "0.38.37"
@ -651,6 +691,12 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "simple-mermaid"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589144a964b4b30fe3a83b4bb1a09e2475aac194ec832a046a23e75bddf9eb29"
[[package]]
name = "strsim"
version = "0.11.1"
@ -738,10 +784,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
name = "wasip2"
version = "1.0.1+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "which"
@ -755,6 +804,12 @@ dependencies = [
"winsafe",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.52.0"
@ -773,6 +828,15 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
@ -843,6 +907,12 @@ version = "0.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
[[package]]
name = "wit-bindgen"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "wyz"
version = "0.5.1"
@ -851,23 +921,3 @@ checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
dependencies = [
"tap",
]
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View file

@ -7,17 +7,16 @@ members = ["crates/*"]
[workspace.package]
version = "0.1.0"
license = "LGPL-3.0-or-later"
edition = "2021"
edition = "2024"
repository = ""
keywords = []
categories = []
rust-version = "1.82.0"
rust-version = "1.89.0"
[workspace.dependencies]
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" }
serde = { version = "1.0.202", features = ["derive"] }
serde_json = { version = "1.0.117", features = ["preserve_order"] }
simple-mermaid = "0.2.0"
[profile.dev]
opt-level = 1

15
README.md Normal file
View file

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

View file

@ -17,7 +17,4 @@ version.workspace = true
[dependencies]
fayalite.workspace = true
serde.workspace = true
name_mangling_serde.workspace = true
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(todo)'] }
simple-mermaid.workspace = true

1
crates/cpu/README.md Symbolic link
View file

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

View file

@ -1,17 +1,17 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
instruction::{PRegNum, CONST_ZERO_UNIT_NUM},
unit::UnitKind,
};
use fayalite::{
intern::{Intern, Interned},
prelude::*,
instruction::{CONST_ZERO_UNIT_NUM, MOpTrait, PRegNum, RenamedMOp, UnitNum, UnitOutRegNum},
unit::{
UnitCancelInput, UnitKind, UnitOutputWrite,
unit_base::{UnitForwardingInfo, UnitToRegAlloc},
},
};
use fayalite::prelude::*;
use serde::{Deserialize, Serialize};
use std::num::NonZeroUsize;
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
#[derive(Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct UnitConfig {
pub kind: UnitKind,
@ -28,14 +28,18 @@ impl UnitConfig {
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
#[derive(Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct CpuConfig {
pub units: Interned<[UnitConfig]>,
pub units: Vec<UnitConfig>,
pub out_reg_num_width: usize,
pub fetch_width: NonZeroUsize,
pub max_branches_per_fetch: NonZeroUsize,
pub max_fetches_in_flight: NonZeroUsize,
pub log2_fetch_width_in_bytes: u8,
/// default value for [`UnitConfig::max_in_flight`]
pub default_unit_max_in_flight: NonZeroUsize,
pub rob_size: NonZeroUsize,
}
impl CpuConfig {
@ -46,18 +50,35 @@ impl CpuConfig {
};
v
};
pub const DEFAULT_MAX_BRANCHES_PER_FETCH: NonZeroUsize = {
let Some(v) = NonZeroUsize::new(1) else {
unreachable!();
};
v
};
pub const DEFAULT_MAX_FETCHES_IN_FLIGHT: NonZeroUsize = {
let Some(v) = NonZeroUsize::new(16) else {
unreachable!();
};
v
};
pub const DEFAULT_LOG2_FETCH_WIDTH_IN_BYTES: u8 = 3;
pub const DEFAULT_UNIT_MAX_IN_FLIGHT: NonZeroUsize = {
let Some(v) = NonZeroUsize::new(8) else {
unreachable!();
};
v
};
pub fn new(units: Interned<[UnitConfig]>) -> Self {
pub fn new(units: Vec<UnitConfig>, rob_size: NonZeroUsize) -> Self {
Self {
units,
out_reg_num_width: Self::DEFAULT_OUT_REG_NUM_WIDTH,
fetch_width: Self::DEFAULT_FETCH_WIDTH,
max_branches_per_fetch: Self::DEFAULT_MAX_BRANCHES_PER_FETCH,
max_fetches_in_flight: Self::DEFAULT_MAX_FETCHES_IN_FLIGHT,
log2_fetch_width_in_bytes: Self::DEFAULT_LOG2_FETCH_WIDTH_IN_BYTES,
default_unit_max_in_flight: Self::DEFAULT_UNIT_MAX_IN_FLIGHT,
rob_size,
}
}
pub fn non_const_unit_nums(&self) -> std::ops::Range<usize> {
@ -66,83 +87,91 @@ impl CpuConfig {
pub fn unit_num_width(&self) -> usize {
UInt::range(CONST_ZERO_UNIT_NUM..self.non_const_unit_nums().end).width()
}
pub fn unit_num(&self) -> UnitNum<DynSize> {
UnitNum[self.unit_num_width()]
}
pub fn unit_out_reg_num(&self) -> UnitOutRegNum<DynSize> {
UnitOutRegNum[self.out_reg_num_width]
}
pub fn p_reg_num(&self) -> PRegNum<DynSize, DynSize> {
PRegNum[self.unit_num_width()][self.out_reg_num_width]
}
pub fn p_reg_num_width(&self) -> usize {
self.unit_num_width() + self.out_reg_num_width
}
pub fn renamed_mop_in_unit(&self) -> RenamedMOp<UnitOutRegNum<DynSize>, DynSize> {
RenamedMOp[self.unit_out_reg_num()][self.p_reg_num_width()]
}
pub fn unit_output_write(&self) -> UnitOutputWrite<DynSize> {
UnitOutputWrite[self.out_reg_num_width]
}
pub fn unit_output_writes(&self) -> Array<HdlOption<UnitOutputWrite<DynSize>>> {
Array[HdlOption[self.unit_output_write()]][self.non_const_unit_nums().len()]
}
pub fn unit_cancel_input(&self) -> UnitCancelInput<DynSize> {
UnitCancelInput[self.out_reg_num_width]
}
pub fn unit_forwarding_info(&self) -> UnitForwardingInfo<DynSize, DynSize, DynSize> {
UnitForwardingInfo[self.unit_num_width()][self.out_reg_num_width]
[self.non_const_unit_nums().len()]
}
pub fn unit_max_in_flight(&self, unit_index: usize) -> NonZeroUsize {
self.units[unit_index]
.max_in_flight
.unwrap_or(self.default_unit_max_in_flight)
}
pub fn retire_queue_index_width(&self) -> usize {
let max_in_flight: usize = (0..self.units.len())
.map(|unit_index| self.unit_max_in_flight(unit_index).get())
.sum();
2 + max_in_flight.next_power_of_two().ilog2() as usize
pub fn unit_to_reg_alloc<
MOp: Type + MOpTrait<DestReg = UnitOutRegNum<DynSize>, SrcRegWidth = DynSize>,
ExtraOut: Type,
>(
&self,
mop_ty: MOp,
extra_out_ty: ExtraOut,
) -> UnitToRegAlloc<MOp, ExtraOut, DynSize, DynSize, DynSize> {
assert_eq!(
mop_ty.dest_reg_ty(),
self.unit_out_reg_num(),
"inconsistent types",
);
UnitToRegAlloc[mop_ty][extra_out_ty][self.unit_num_width()][self.out_reg_num_width]
[self.non_const_unit_nums().len()]
}
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")
}
}
mod sealed {
pub trait Sealed {}
#[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.rob_size.get()))]
pub type CpuConfigRobSize<C: PhantomConstGet<CpuConfig>> = DynSize;
pub trait PhantomConstCpuConfig:
PhantomConstGet<CpuConfig>
+ Into<PhantomConst<CpuConfig>>
+ From<PhantomConst<CpuConfig>>
+ Type
+ ToSimValue<Type = Self>
+ ToExpr<Type = Self>
{
}
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();
}
impl PhantomConstCpuConfig for PhantomConst<CpuConfig> {}

View file

@ -1,16 +1,12 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
config::{CpuConfigType, UnitNumWidth, UnitOutRegNumWidth},
unit::UnitMOp,
util::range_u32_len,
};
use crate::{unit::UnitMOp, util::range_u32_len};
use fayalite::{
expr::ops::{ArrayLiteral, ExprPartialEq},
expr::{HdlPartialEqImpl, ops::ArrayLiteral},
intern::Interned,
prelude::*,
};
use std::{fmt, marker::PhantomData, ops::Range};
use std::{borrow::Cow, fmt, marker::PhantomData, ops::Range};
pub mod power_isa;
@ -29,10 +25,7 @@ impl<T: MOpTrait> MOpInto<T> for T {
}
pub trait MOpTrait: Type {
type Mapped<NewDestReg: Type, NewSrcRegWidth: Size>: MOpTrait<
DestReg = NewDestReg,
SrcRegWidth = NewSrcRegWidth,
>;
type Mapped<NewDestReg: Type, NewSrcRegWidth: Size>: MOpTrait<DestReg = NewDestReg, SrcRegWidth = NewSrcRegWidth>;
type DestReg: Type;
type SrcRegWidth: Size;
fn dest_reg_ty(self) -> Self::DestReg;
@ -77,11 +70,11 @@ pub trait CommonMOpTrait: MOpTrait {
type PrefixPad: KnownSize;
type SrcCount: KnownSize;
type CommonMOpTraitMapped<NewDestReg: Type, NewSrcRegWidth: Size>: CommonMOpTrait<
DestReg = NewDestReg,
SrcRegWidth = NewSrcRegWidth,
PrefixPad = Self::PrefixPad,
SrcCount = Self::SrcCount,
>;
DestReg = NewDestReg,
SrcRegWidth = NewSrcRegWidth,
PrefixPad = Self::PrefixPad,
SrcCount = Self::SrcCount,
>;
type CommonMOpTraitDestReg: Type;
type CommonMOpTraitSrcRegWidth: Size;
fn common_mop_ty(
@ -178,13 +171,37 @@ pub enum OutputIntegerMode {
SignExt8,
}
impl ExprPartialEq<Self> for OutputIntegerMode {
fn cmp_eq(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
lhs.cast_to_bits().cmp_eq(rhs.cast_to_bits())
impl HdlPartialEqImpl<Self> for OutputIntegerMode {
#[track_caller]
fn cmp_value_eq(
lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
rhs: Self,
rhs_value: Cow<'_, Self::SimValue>,
) -> bool {
SimValue::opaque(&SimValue::from_value(lhs, lhs_value.into_owned()))
== SimValue::opaque(&SimValue::from_value(rhs, rhs_value.into_owned()))
}
fn cmp_ne(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
lhs.cast_to_bits().cmp_ne(rhs.cast_to_bits())
#[track_caller]
fn cmp_sim_value_eq(
lhs: Cow<'_, SimValue<Self>>,
rhs: Cow<'_, SimValue<Self>>,
) -> SimValue<Bool> {
(SimValue::opaque(&lhs) == SimValue::opaque(&rhs)).to_sim_value()
}
#[track_caller]
fn cmp_sim_value_ne(
lhs: Cow<'_, SimValue<Self>>,
rhs: Cow<'_, SimValue<Self>>,
) -> SimValue<Bool> {
(SimValue::opaque(&lhs) != SimValue::opaque(&rhs)).to_sim_value()
}
#[track_caller]
fn cmp_expr_eq(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
lhs.cast_to_bits().cmp_eq(rhs.cast_to_bits())
}
}
@ -296,8 +313,8 @@ impl<PrefixPad: KnownSize, DestReg: Type, SrcRegWidth: Size, SrcCount: KnownSize
let dest = dest.to_expr();
let src_in = src.to_expr();
let imm = imm.to_expr();
assert_eq!(Expr::ty(imm), Self::imm_ty());
let src_reg_ty = Expr::ty(src_in).element();
assert_eq!(imm.ty(), Self::imm_ty());
let src_reg_ty = src_in.ty().element();
let imm_parts = imm.cast_to_bits().cast_bits_to(Self::imm_parts_ty());
let mut src = [0_hdl_u0.cast_to(src_reg_ty); COMMON_MOP_SRC_LEN];
for i in 0..SrcCount::VALUE {
@ -341,9 +358,9 @@ impl<PrefixPad: KnownSize, DestReg: Type, SrcRegWidth: Size, SrcCount: KnownSize
#[hdl]
pub fn connect_to_imm(expr: impl ToExpr<Type = Self>, imm: impl ToExpr<Type = SInt>) {
let expr = expr.to_expr();
let src_reg_ty = Expr::ty(expr).src.element();
let src_reg_ty = expr.ty().src.element();
let imm = imm.to_expr();
assert_eq!(Expr::ty(imm), Self::imm_ty());
assert_eq!(imm.ty(), Self::imm_ty());
let imm_parts = imm.cast_to_bits().cast_bits_to(Self::imm_parts_ty());
let mut src = [Some(0_hdl_u0.cast_to(src_reg_ty)); COMMON_MOP_SRC_LEN];
for i in 0..SrcCount::VALUE {
@ -496,7 +513,7 @@ macro_rules! mop_enum {
fn dest_reg(input: impl ToExpr<Type = Self>) -> Expr<Self::DestReg> {
let input = input.to_expr();
#[hdl]
let dest_reg = wire(Expr::ty(input).dest_reg_ty());
let dest_reg = wire(input.ty().dest_reg_ty());
#[hdl]
match input {
Self::$FirstVariant(v) => connect(dest_reg, <$first_ty as MOpTrait>::dest_reg(v)),
@ -537,7 +554,7 @@ macro_rules! mop_enum {
) -> Expr<Self::Mapped<NewDestReg, NewSrcRegWidth>> {
let input = input.to_expr();
let new_dest = new_dest.to_expr();
let mapped_ty = Expr::ty(input).mapped_ty(Expr::ty(new_dest), new_src_reg_width);
let mapped_ty = input.ty().mapped_ty(new_dest.ty(), new_src_reg_width);
#[hdl]
let mapped_regs = wire(mapped_ty);
#[hdl]
@ -584,7 +601,7 @@ macro_rules! mop_enum {
MOpInto::mop_into_ty($MOp[MOpTrait::dest_reg_ty(self)][MOpTrait::src_reg_width(self)]$([$sizes_get_size(self)])*)
}
fn mop_into(this: Expr<Self>) -> Expr<Target> {
MOpInto::mop_into(MOpInto::<$MOp<$DestReg, $SrcRegWidth, $($Sizes,)*>>::mop_into_ty(Expr::ty(this)).$Variant(this))
MOpInto::mop_into(MOpInto::<$MOp<$DestReg, $SrcRegWidth, $($Sizes,)*>>::mop_into_ty(this.ty()).$Variant(this))
}
}
};
@ -809,21 +826,19 @@ common_mop_struct! {
}
}
#[hdl(cmp_eq, no_static)]
#[hdl(cmp_eq)]
/// there may be more than one unit of a given kind, so UnitNum is not the same as UnitKind.
/// zero is used for built-in constants, such as the zero register
pub struct UnitNum<C: Type + CpuConfigType> {
pub adj_value: UIntType<UnitNumWidth<C>>,
pub config: C,
pub struct UnitNum<Width: Size> {
pub adj_value: UIntType<Width>,
}
impl<C: Type + CpuConfigType> UnitNum<C> {
impl<Width: Size> UnitNum<Width> {
#[hdl]
pub fn const_zero(self) -> Expr<Self> {
#[hdl]
UnitNum {
adj_value: CONST_ZERO_UNIT_NUM.cast_to(self.adj_value),
config: self.config,
}
}
#[hdl]
@ -831,27 +846,23 @@ impl<C: Type + CpuConfigType> UnitNum<C> {
#[hdl]
UnitNum {
adj_value: (index + 1).cast_to(self.adj_value),
config: self.config,
}
}
pub fn is_index(expr: impl ToExpr<Type = Self>, index: usize) -> Expr<Bool> {
let expr = expr.to_expr();
Expr::ty(expr)
.from_index(index)
.adj_value
.cmp_eq(expr.adj_value)
expr.ty().from_index(index).adj_value.cmp_eq(expr.adj_value)
}
#[hdl]
pub fn as_index(expr: impl ToExpr<Type = Self>) -> Expr<HdlOption<UIntType<DynSize>>> {
pub fn as_index(expr: impl ToExpr<Type = Self>) -> Expr<HdlOption<UIntType<Width>>> {
let expr = expr.to_expr();
#[hdl]
let unit_index = wire(HdlOption[Expr::ty(expr).adj_value]);
connect(unit_index, Expr::ty(unit_index).HdlNone());
let unit_index = wire(HdlOption[expr.ty().adj_value]);
connect(unit_index, unit_index.ty().HdlNone());
#[hdl]
if expr.adj_value.cmp_ne(0u8) {
connect(
unit_index,
HdlSome((expr.adj_value - 1u8).cast_to(Expr::ty(expr).adj_value)),
HdlSome((expr.adj_value - 1u8).cast_to(expr.ty().adj_value)),
);
}
unit_index
@ -860,20 +871,19 @@ impl<C: Type + CpuConfigType> UnitNum<C> {
pub const CONST_ZERO_UNIT_NUM: usize = 0;
#[hdl(cmp_eq, no_static)]
pub struct UnitOutRegNum<C: Type + CpuConfigType> {
pub value: UIntType<UnitOutRegNumWidth<C>>,
pub config: C,
#[hdl(cmp_eq)]
pub struct UnitOutRegNum<Width: Size> {
pub value: UIntType<Width>,
}
#[hdl(cmp_eq, no_static)]
#[hdl(cmp_eq)]
/// Physical Register Number -- registers in the CPU's backend
pub struct PRegNum<C: Type + CpuConfigType> {
pub unit_num: UnitNum<C>,
pub unit_out_reg: UnitOutRegNum<C>,
pub struct PRegNum<UnitNumWidth: Size, OutRegNumWidth: Size> {
pub unit_num: UnitNum<UnitNumWidth>,
pub unit_out_reg: UnitOutRegNum<OutRegNumWidth>,
}
impl<C: Type + CpuConfigType> PRegNum<C> {
impl<UnitNumWidth: Size, OutRegNumWidth: Size> PRegNum<UnitNumWidth, OutRegNumWidth> {
#[hdl]
pub fn const_zero(self) -> Expr<Self> {
#[hdl]
@ -882,7 +892,6 @@ impl<C: Type + CpuConfigType> PRegNum<C> {
unit_out_reg: #[hdl]
UnitOutRegNum {
value: 0u8.cast_to(self.unit_out_reg.value),
config: self.unit_out_reg.config,
},
}
}
@ -905,7 +914,7 @@ impl MOpRegNum {
pub fn const_zero() -> Expr<Self> {
#[hdl]
MOpRegNum {
value: Self::CONST_ZERO_REG_NUM.cast_to_static(),
value: Self::CONST_ZERO_REG_NUM.cast_to_static::<UInt<_>>(),
}
}
/// a lot of instructions write to flag registers that we want
@ -919,9 +928,11 @@ impl MOpRegNum {
//
// TODO: maybe add more registers later.
pub const FLAG_REG_NUMS: Range<u32> = 0xFE..0x100;
/// registers that aren't constants
pub const NON_CONST_REG_NUMS: Range<u32> =
Self::CONST_ZERO_REG_NUM + 1..Self::FLAG_REG_NUMS.end;
/// registers handled by a special small rename table (for flags and stuff, since it has more read/write ports)
pub const SPECIAL_REG_NUMS: Range<u32> = Self::FLAG_REG_NUMS;
/// registers handled by the large rename table for normal registers (has less read/write ports)
pub const NORMAL_REG_NUMS: Range<u32> =
Self::CONST_ZERO_REG_NUM + 1..Self::SPECIAL_REG_NUMS.start;
}
#[hdl(cmp_eq)]
@ -936,6 +947,29 @@ pub struct MOpDestReg {
pub flag_regs: Array<HdlOption<()>, { range_u32_len(&MOpRegNum::FLAG_REG_NUMS) }>,
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum RenameTableName {
/// the large rename table for normal registers (has less read/write ports)
Normal,
/// a special small rename table (for flags and stuff, since it has more read/write ports)
Special,
}
impl RenameTableName {
pub const fn reg_range(self) -> std::ops::Range<u32> {
match self {
Self::Normal => MOpRegNum::NORMAL_REG_NUMS,
Self::Special => MOpRegNum::SPECIAL_REG_NUMS,
}
}
pub const fn as_str(self) -> &'static str {
match self {
Self::Normal => "rename_table_normal",
Self::Special => "rename_table_special",
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum MOpDestRegKind {
NormalReg {
@ -973,13 +1007,16 @@ impl fmt::Display for MOpDestRegName {
}
impl MOpDestRegKind {
pub const fn reg_num_range(self) -> std::ops::Range<u32> {
pub const fn reg_range(self) -> std::ops::Range<u32> {
match self {
Self::NormalReg { dest_reg_index: _ } => MOpRegNum::NON_CONST_REG_NUMS,
Self::FlagReg {
reg_num,
flag_reg_index: _,
} => reg_num..reg_num + 1,
Self::NormalReg { .. } => MOpRegNum::NORMAL_REG_NUMS,
Self::FlagReg { .. } => MOpRegNum::FLAG_REG_NUMS,
}
}
pub const fn rename_table_names(self) -> &'static [RenameTableName] {
match self {
Self::NormalReg { .. } => &[RenameTableName::Normal, RenameTableName::Special],
Self::FlagReg { .. } => &[RenameTableName::Special],
}
}
pub fn fixed_reg_num(self) -> Option<u32> {
@ -1054,7 +1091,7 @@ impl MOpDestReg {
flag_reg,
#[hdl]
MOpRegNum {
value: reg_num.cast_to_static(),
value: reg_num.cast_to_static::<UInt<_>>(),
},
);
}
@ -1072,5 +1109,5 @@ pub type MOp = UnitMOp<
>;
#[hdl]
pub type RenamedMOp<SrcRegWidth: Size> =
UnitMOp<(), SrcRegWidth, L2RegisterFileMOp<(), SrcRegWidth>>;
pub type RenamedMOp<DestReg: Type, SrcRegWidth: Size> =
UnitMOp<DestReg, SrcRegWidth, L2RegisterFileMOp<DestReg, SrcRegWidth>>;

View file

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

View file

@ -2,10 +2,8 @@
// See Notices.txt for copyright information
pub mod config;
pub mod instruction;
pub mod instruction_rename;
pub mod next_pc;
pub mod reg_alloc;
pub mod register;
pub mod rename_table;
pub mod retire_queue;
pub mod unit;
pub mod util;

4862
crates/cpu/src/next_pc.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,24 @@
%% 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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,19 +2,18 @@
// See Notices.txt for copyright information
use crate::{
config::{CpuConfig, CpuConfigType, RetireQueueIndexWidth, UnitCount},
config::CpuConfig,
instruction::{
mop_enum, AluBranchMOp, LoadStoreMOp, MOp, MOpDestReg, MOpInto, MOpRegNum, MOpTrait,
RenamedMOp, UnitOutRegNum,
AluBranchMOp, LoadStoreMOp, MOp, MOpDestReg, MOpInto, MOpRegNum, MOpTrait, RenamedMOp,
UnitOutRegNum, mop_enum,
},
register::{FlagsMode, PRegValue},
unit::unit_base::UnitToRegAlloc,
};
use fayalite::{
bundle::{Bundle, BundleType},
int::BoolOrIntType,
intern::{Intern, Interned},
prelude::*,
util::ready_valid::ReadyValid,
};
use serde::{Deserialize, Serialize};
@ -47,16 +46,23 @@ macro_rules! all_units {
}
impl $UnitKind {
pub fn unit(self, config: PhantomConst<CpuConfig>, unit_index: usize) -> DynUnit {
pub fn unit(self, config: &CpuConfig, unit_index: usize) -> DynUnit {
match self {
$($UnitKind::$Unit => $create_dyn_unit_fn(config, unit_index),)*
}
}
}
impl ToExpr for $UnitKind {
impl ValueType for $UnitKind {
type Type = $HdlUnitKind;
type ValueCategory = fayalite::expr::value_category::ValueCategoryExpr;
fn ty(&self) -> Self::Type {
$HdlUnitKind
}
}
impl ToExpr for $UnitKind {
fn to_expr(&self) -> Expr<Self::Type> {
match self {
$($UnitKind::$Unit => $HdlUnitKind.$Unit(),)*
@ -100,7 +106,7 @@ macro_rules! all_units {
#[hdl]
$vis fn $extract(expr: impl ToExpr<Type = Self>) -> Expr<HdlOption<$Op>> {
let expr = expr.to_expr();
let ty = Expr::ty(expr);
let ty = expr.ty();
#[hdl]
let $extract = wire(HdlOption[ty.$Unit]);
connect($extract, HdlOption[ty.$Unit].HdlNone());
@ -166,10 +172,10 @@ macro_rules! all_units {
$TransformedMoveOp: MOpTrait<DestReg = $DestReg, SrcRegWidth = $SrcRegWidth>,
{
let this = this.to_expr();
let new_ty = Expr::ty(this).with_transformed_move_op_ty(new_transformed_move_op_ty);
let new_ty = this.ty().with_transformed_move_op_ty(new_transformed_move_op_ty);
#[hdl]
let with_transformed_move_op = wire(HdlOption[new_ty]);
connect(with_transformed_move_op, Expr::ty(with_transformed_move_op).HdlNone());
connect(with_transformed_move_op, with_transformed_move_op.ty().HdlNone());
// workaround #[hdl] match expanding to a loop, so you can't move variables in it
let mut connect_transformed_move_op = Some(connect_transformed_move_op);
#[hdl]
@ -206,28 +212,23 @@ macro_rules! all_units {
})*
};
const _: () = {
#[hdl]
type $DestReg = ();
$(impl<$DestReg: Type, $SrcRegWidth: Size> MOpInto<RenamedMOp<$DestReg, $SrcRegWidth>> for $BeforeOp {
fn mop_into_ty(self) -> RenamedMOp<$DestReg, $SrcRegWidth> {
RenamedMOp[MOpTrait::dest_reg_ty(self)][MOpTrait::src_reg_width(self)]
}
fn mop_into(this: Expr<Self>) -> Expr<RenamedMOp<$DestReg, $SrcRegWidth>> {
MOpInto::<RenamedMOp<$DestReg, $SrcRegWidth>>::mop_into_ty(this.ty()).$BeforeUnit(this)
}
})*
$(impl<$SrcRegWidth: Size> MOpInto<RenamedMOp<$SrcRegWidth>> for $BeforeOp {
fn mop_into_ty(self) -> RenamedMOp<$SrcRegWidth> {
RenamedMOp[MOpTrait::src_reg_width(self)]
}
fn mop_into(this: Expr<Self>) -> Expr<RenamedMOp<$SrcRegWidth>> {
MOpInto::<RenamedMOp<$SrcRegWidth>>::mop_into_ty(Expr::ty(this)).$BeforeUnit(this)
}
})*
$(impl<$SrcRegWidth: Size> MOpInto<RenamedMOp<$SrcRegWidth>> for $AfterOp {
fn mop_into_ty(self) -> RenamedMOp<$SrcRegWidth> {
RenamedMOp[MOpTrait::src_reg_width(self)]
}
fn mop_into(this: Expr<Self>) -> Expr<RenamedMOp<$SrcRegWidth>> {
MOpInto::<RenamedMOp<$SrcRegWidth>>::mop_into_ty(Expr::ty(this)).$AfterUnit(this)
}
})*
};
$(impl<$DestReg: Type, $SrcRegWidth: Size> MOpInto<RenamedMOp<$DestReg, $SrcRegWidth>> for $AfterOp {
fn mop_into_ty(self) -> RenamedMOp<$DestReg, $SrcRegWidth> {
RenamedMOp[MOpTrait::dest_reg_ty(self)][MOpTrait::src_reg_width(self)]
}
fn mop_into(this: Expr<Self>) -> Expr<RenamedMOp<$DestReg, $SrcRegWidth>> {
MOpInto::<RenamedMOp<$DestReg, $SrcRegWidth>>::mop_into_ty(this.ty()).$AfterUnit(this)
}
})*
};
}
@ -260,80 +261,18 @@ 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>,
#[hdl(cmp_eq)]
pub struct UnitOutputWrite<OutRegNumWidth: Size> {
pub which: UnitOutRegNum<OutRegNumWidth>,
pub value: PRegValue,
}
#[hdl]
pub type UnitOutputWrites<C: Type + CpuConfigType> =
ArrayType<HdlOption<UnitOutputWrite<C>>, UnitCount<C>>;
#[hdl(cmp_eq)]
pub struct TrapData {
// TODO
@ -351,31 +290,21 @@ impl<ExtraOut: Type> UnitResult<ExtraOut> {
}
}
#[hdl(no_static)]
pub struct UnitOutput<C: Type + CpuConfigType, ExtraOut> {
pub dest: UnitOutRegNum<C>,
pub retire_queue_index: RetireQueueIndex<C>,
#[hdl]
pub struct UnitOutput<OutRegNumWidth: Size, ExtraOut> {
pub which: UnitOutRegNum<OutRegNumWidth>,
pub result: UnitResult<ExtraOut>,
}
impl<C: Type + CpuConfigType, ExtraOut: Type> UnitOutput<C, ExtraOut> {
impl<OutRegNumWidth: Size, ExtraOut: Type> UnitOutput<OutRegNumWidth, 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)
}
#[hdl(cmp_eq)]
pub struct UnitCancelInput<OutRegNumWidth: Size> {
pub which: UnitOutRegNum<OutRegNumWidth>,
}
pub trait UnitTrait:
@ -391,14 +320,17 @@ pub trait UnitTrait:
fn unit_kind(&self) -> UnitKind;
fn extract_mop(&self, mop: Expr<RenamedMOp<DynSize>>) -> Expr<HdlOption<Self::MOp>>;
fn extract_mop(
&self,
mop: Expr<RenamedMOp<UnitOutRegNum<DynSize>, DynSize>>,
) -> Expr<HdlOption<Self::MOp>>;
fn module(&self) -> Interned<Module<Self::Type>>;
fn unit_to_reg_alloc(
&self,
this: Expr<Self::Type>,
) -> Expr<UnitToRegAlloc<PhantomConst<CpuConfig>, Self::MOp, Self::ExtraOut>>;
) -> Expr<UnitToRegAlloc<Self::MOp, Self::ExtraOut, DynSize, DynSize, DynSize>>;
fn cd(&self, this: Expr<Self::Type>) -> Expr<ClockDomain>;
@ -446,7 +378,10 @@ impl UnitTrait for DynUnit {
self.unit_kind
}
fn extract_mop(&self, mop: Expr<RenamedMOp<DynSize>>) -> Expr<HdlOption<Self::MOp>> {
fn extract_mop(
&self,
mop: Expr<RenamedMOp<UnitOutRegNum<DynSize>, DynSize>>,
) -> Expr<HdlOption<Self::MOp>> {
self.unit.extract_mop(mop)
}
@ -457,7 +392,7 @@ impl UnitTrait for DynUnit {
fn unit_to_reg_alloc(
&self,
this: Expr<Self::Type>,
) -> Expr<UnitToRegAlloc<PhantomConst<CpuConfig>, Self::MOp, Self::ExtraOut>> {
) -> Expr<UnitToRegAlloc<Self::MOp, Self::ExtraOut, DynSize, DynSize, DynSize>> {
self.unit.unit_to_reg_alloc(this)
}
@ -498,7 +433,10 @@ impl<T: UnitTrait + Clone + std::hash::Hash + Eq> UnitTrait for DynUnitWrapper<T
self.0.unit_kind()
}
fn extract_mop(&self, mop: Expr<RenamedMOp<DynSize>>) -> Expr<HdlOption<Self::MOp>> {
fn extract_mop(
&self,
mop: Expr<RenamedMOp<UnitOutRegNum<DynSize>, DynSize>>,
) -> Expr<HdlOption<Self::MOp>> {
Expr::from_enum(Expr::as_enum(self.0.extract_mop(mop)))
}
@ -509,7 +447,7 @@ impl<T: UnitTrait + Clone + std::hash::Hash + Eq> UnitTrait for DynUnitWrapper<T
fn unit_to_reg_alloc(
&self,
this: Expr<Self::Type>,
) -> Expr<UnitToRegAlloc<PhantomConst<CpuConfig>, Self::MOp, Self::ExtraOut>> {
) -> Expr<UnitToRegAlloc<Self::MOp, Self::ExtraOut, DynSize, DynSize, DynSize>> {
Expr::from_bundle(Expr::as_bundle(
self.0.unit_to_reg_alloc(Expr::from_bundle(this)),
))

View file

@ -2,26 +2,29 @@
// See Notices.txt for copyright information
use crate::{
config::{CpuConfig, PRegNumWidth},
config::CpuConfig,
instruction::{
AddSubMOp, AluBranchMOp, AluCommonMOp, CommonMOp, LogicalMOp, OutputIntegerMode,
RenamedMOp, COMMON_MOP_SRC_LEN,
AddSubMOp, AluBranchMOp, AluCommonMOp, COMMON_MOP_SRC_LEN, CommonMOp, LogicalMOp, MOpTrait,
OutputIntegerMode, RenamedMOp, UnitOutRegNum,
},
register::{FlagsMode, PRegFlagsPowerISA, PRegFlagsX86, PRegValue},
unit::{
unit_base::{unit_base, ExecuteEnd, ExecuteStart},
DynUnit, DynUnitWrapper, GlobalState, UnitKind, UnitMOp, UnitOutput, UnitResult,
UnitResultCompleted, UnitToRegAlloc, UnitTrait,
UnitResultCompleted, UnitTrait,
unit_base::{ExecuteEnd, ExecuteStart, UnitToRegAlloc, unit_base},
},
};
use fayalite::{
intern::Interned, module::wire_with_loc, prelude::*, util::ready_valid::ReadyValid,
intern::{Intern, Interned},
module::wire_with_loc,
prelude::*,
util::ready_valid::ReadyValid,
};
use std::{collections::HashMap, ops::RangeTo};
#[hdl]
fn add_sub<SrcCount: KnownSize>(
mop: Expr<AddSubMOp<(), DynSize, SrcCount>>,
mop: Expr<AddSubMOp<UnitOutRegNum<DynSize>, DynSize, SrcCount>>,
pc: Expr<UInt<64>>,
flags_mode: Expr<FlagsMode>,
src_values: Expr<Array<PRegValue, { COMMON_MOP_SRC_LEN }>>,
@ -229,7 +232,7 @@ fn add_sub<SrcCount: KnownSize>(
#[hdl]
fn logical(
mop: Expr<LogicalMOp<(), DynSize>>,
mop: Expr<LogicalMOp<UnitOutRegNum<DynSize>, DynSize>>,
flags_mode: Expr<FlagsMode>,
src_values: Expr<Array<PRegValue, { COMMON_MOP_SRC_LEN }>>,
) -> Expr<UnitResultCompleted<()>> {
@ -242,15 +245,20 @@ fn logical(
}
#[hdl_module]
pub fn alu_branch(config: PhantomConst<CpuConfig>, unit_index: usize) {
pub fn alu_branch(config: &CpuConfig, unit_index: usize) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let unit_to_reg_alloc: UnitToRegAlloc<
PhantomConst<CpuConfig>,
AluBranchMOp<(), DynSize>,
AluBranchMOp<UnitOutRegNum<DynSize>, DynSize>,
(),
> = m.output(UnitToRegAlloc[config][AluBranchMOp[()][PRegNumWidth[config]]][()]);
DynSize,
DynSize,
DynSize,
> = m.output(config.unit_to_reg_alloc(
AluBranchMOp[config.unit_out_reg_num()][config.p_reg_num_width()],
(),
));
#[hdl]
let global_state: GlobalState = m.input();
@ -258,22 +266,23 @@ pub fn alu_branch(config: PhantomConst<CpuConfig>, unit_index: usize) {
let unit_base = instance(unit_base(
config,
unit_index,
Expr::ty(unit_to_reg_alloc).input.data.HdlSome.mop,
unit_to_reg_alloc.ty().input.data.HdlSome.mop,
(),
));
connect(unit_to_reg_alloc, unit_base.unit_to_reg_alloc);
connect(unit_base.cd, cd);
connect(unit_base.execute_start.ready, true);
connect(
unit_base.execute_end,
Expr::ty(unit_base.execute_end).HdlNone(),
);
connect(unit_base.execute_end, unit_base.execute_end.ty().HdlNone());
#[hdl]
if let HdlSome(execute_start) = ReadyValid::firing_data(unit_base.execute_start) {
#[hdl]
let ExecuteStart::<_, _> { insn, src_values } = execute_start;
let ExecuteStart::<_> {
mop,
pc,
src_values,
} = execute_start;
#[hdl]
match insn.mop {
match mop {
AluBranchMOp::<_, _>::AddSub(mop) => connect(
unit_base.execute_end,
HdlSome(
@ -281,11 +290,10 @@ pub fn alu_branch(config: PhantomConst<CpuConfig>, unit_index: usize) {
ExecuteEnd::<_, _> {
unit_output: #[hdl]
UnitOutput::<_, _> {
dest: insn.dest,
retire_queue_index: insn.retire_queue_index,
which: MOpTrait::dest_reg(mop),
result: UnitResult[()].Completed(add_sub(
mop,
insn.pc,
pc,
global_state.flags_mode,
src_values,
)),
@ -300,11 +308,10 @@ pub fn alu_branch(config: PhantomConst<CpuConfig>, unit_index: usize) {
ExecuteEnd::<_, _> {
unit_output: #[hdl]
UnitOutput::<_, _> {
dest: insn.dest,
retire_queue_index: insn.retire_queue_index,
which: MOpTrait::dest_reg(mop),
result: UnitResult[()].Completed(add_sub(
mop,
insn.pc,
pc,
global_state.flags_mode,
src_values,
)),
@ -319,8 +326,7 @@ pub fn alu_branch(config: PhantomConst<CpuConfig>, unit_index: usize) {
ExecuteEnd::<_, _> {
unit_output: #[hdl]
UnitOutput::<_, _> {
dest: insn.dest,
retire_queue_index: insn.retire_queue_index,
which: MOpTrait::dest_reg(mop),
result: UnitResult[()].Completed(logical(
mop,
global_state.flags_mode,
@ -336,14 +342,14 @@ pub fn alu_branch(config: PhantomConst<CpuConfig>, unit_index: usize) {
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct AluBranch {
config: PhantomConst<CpuConfig>,
config: Interned<CpuConfig>,
module: Interned<Module<alu_branch>>,
}
impl AluBranch {
pub fn new(config: PhantomConst<CpuConfig>, unit_index: usize) -> Self {
pub fn new(config: &CpuConfig, unit_index: usize) -> Self {
Self {
config,
config: config.intern(),
module: alu_branch(config, unit_index),
}
}
@ -352,7 +358,7 @@ impl AluBranch {
impl UnitTrait for AluBranch {
type Type = alu_branch;
type ExtraOut = ();
type MOp = AluBranchMOp<(), DynSize>;
type MOp = AluBranchMOp<UnitOutRegNum<DynSize>, DynSize>;
fn ty(&self) -> Self::Type {
self.module.io_ty()
@ -370,7 +376,10 @@ impl UnitTrait for AluBranch {
UnitKind::AluBranch
}
fn extract_mop(&self, mop: Expr<RenamedMOp<DynSize>>) -> Expr<HdlOption<Self::MOp>> {
fn extract_mop(
&self,
mop: Expr<RenamedMOp<UnitOutRegNum<DynSize>, DynSize>>,
) -> Expr<HdlOption<Self::MOp>> {
UnitMOp::alu_branch_mop(mop)
}
@ -381,7 +390,7 @@ impl UnitTrait for AluBranch {
fn unit_to_reg_alloc(
&self,
this: Expr<Self::Type>,
) -> Expr<UnitToRegAlloc<PhantomConst<CpuConfig>, Self::MOp, Self::ExtraOut>> {
) -> Expr<UnitToRegAlloc<Self::MOp, Self::ExtraOut, DynSize, DynSize, DynSize>> {
this.unit_to_reg_alloc
}

View file

@ -2,31 +2,72 @@
// See Notices.txt for copyright information
use crate::{
config::{CpuConfig, CpuConfigType, UnitOutRegNumWidth},
instruction::{MOpTrait, PRegNum, UnitNum, UnitOutRegNum, COMMON_MOP_SRC_LEN},
config::CpuConfig,
instruction::{COMMON_MOP_SRC_LEN, MOpTrait, PRegNum, UnitNum, UnitOutRegNum},
register::PRegValue,
unit::{
RenamedInsnData, RetireQueueIndex, UnitCancelInput, UnitForwardingInfo, UnitOutput,
UnitOutputWrite, UnitToRegAlloc,
},
unit::{UnitCancelInput, UnitOutput, UnitOutputWrite},
util::tree_reduce::tree_reduce,
};
use fayalite::{
memory::splat_mask,
module::{memory_with_loc, wire_with_loc},
prelude::*,
ty::StaticType,
util::{prefix_sum::reduce, ready_valid::ReadyValid},
util::ready_valid::ReadyValid,
};
use std::marker::PhantomData;
#[hdl(no_static)]
pub struct ExecuteStart<C: Type + CpuConfigType, MOp: Type> {
pub insn: RenamedInsnData<C, MOp, UnitOutRegNum<C>>,
#[hdl]
pub struct UnitForwardingInfo<UnitNumWidth: Size, OutRegNumWidth: Size, UnitCount: Size> {
pub unit_output_writes: ArrayType<HdlOption<UnitOutputWrite<OutRegNumWidth>>, UnitCount>,
pub unit_reg_frees: ArrayType<HdlOption<UnitOutRegNum<OutRegNumWidth>>, UnitCount>,
pub _phantom: PhantomData<UnitNumWidth>,
}
#[hdl]
pub struct UnitInput<MOp: Type> {
pub mop: MOp,
pub pc: UInt<64>,
}
#[hdl]
pub struct UnitToRegAlloc<
MOp: Type,
ExtraOut: Type,
UnitNumWidth: Size,
OutRegNumWidth: Size,
UnitCount: Size,
> {
#[hdl(flip)]
pub unit_forwarding_info: UnitForwardingInfo<UnitNumWidth, OutRegNumWidth, UnitCount>,
#[hdl(flip)]
pub input: ReadyValid<UnitInput<MOp>>,
#[hdl(flip)]
pub cancel_input: HdlOption<UnitCancelInput<OutRegNumWidth>>,
pub output: HdlOption<UnitOutput<OutRegNumWidth, ExtraOut>>,
}
impl<MOp: Type, ExtraOut: Type, UnitNumWidth: Size, OutRegNumWidth: Size, UnitCount: Size>
UnitToRegAlloc<MOp, ExtraOut, UnitNumWidth, OutRegNumWidth, UnitCount>
{
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]
pub struct ExecuteStart<MOp: Type + MOpTrait<DestReg = UnitOutRegNum<DynSize>>> {
pub mop: MOp,
pub pc: UInt<64>,
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]
pub struct ExecuteEnd<OutRegNumWidth: Size, ExtraOut> {
pub unit_output: UnitOutput<OutRegNumWidth, ExtraOut>,
}
#[hdl]
@ -106,34 +147,27 @@ impl InFlightOpState {
}
}
#[hdl(no_static)]
struct InFlightOp<C: Type + CpuConfigType, MOp: Type> {
#[hdl]
struct InFlightOp<MOp: Type> {
state: InFlightOpState,
insn: RenamedInsnData<C, MOp, UnitOutRegNum<C>>,
mop: MOp,
pc: UInt<64>,
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> {
#[hdl]
struct InFlightOpsSummary<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> {
impl<OpIndexWidth: Size> InFlightOpsSummary<OpIndexWidth> {
#[hdl]
fn new<MOp: Type>(
op_index: usize,
op_index_ty: UIntType<OpIndexWidth>,
in_flight_op: impl ToExpr<Type = HdlOption<InFlightOp<C, MOp>>>,
in_flight_op: impl ToExpr<Type = HdlOption<InFlightOp<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(),
@ -146,73 +180,34 @@ impl<C: Type + CpuConfigType, OpIndexWidth: Size> InFlightOpsSummary<C, OpIndexW
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::<_, _> {
let InFlightOp::<_> {
state,
insn,
mop: _,
pc: _,
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 => {
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),
);
}
InFlightOpState::CanceledAndRunning | InFlightOpState::Running => {}
}
} else {
connect(empty_op_index, HdlSome(op_index.cast_to(op_index_ty)));
}
#[hdl]
InFlightOpsSummary::<_, _> {
InFlightOpsSummary::<_> {
empty_op_index,
ready_op_index,
ready_for_retire_queue_index,
}
}
#[hdl]
@ -220,60 +215,22 @@ impl<C: Type + CpuConfigType, OpIndexWidth: Size> InFlightOpsSummary<C, OpIndexW
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::<_, _> {
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> {
impl InFlightOpsSummary<DynSize> {
fn summarize<MOp: Type, MaxInFlight: Size>(
in_flight_ops: impl ToExpr<
Type = ArrayType<HdlOption<InFlightOp<PhantomConst<CpuConfig>, MOp>>, MaxInFlight>,
>,
in_flight_ops: impl ToExpr<Type = ArrayType<HdlOption<InFlightOp<MOp>>, MaxInFlight>>,
) -> Expr<Self> {
let in_flight_ops = in_flight_ops.to_expr();
let max_in_flight = Expr::ty(in_flight_ops).len();
let max_in_flight = in_flight_ops.ty().len();
let index_range = 0..max_in_flight;
let index_ty = UInt::range(index_range.clone());
reduce(
tree_reduce(
index_range.map(|i| Self::new(i, index_ty, in_flight_ops[i])),
Self::combine,
)
@ -282,8 +239,11 @@ impl InFlightOpsSummary<PhantomConst<CpuConfig>, DynSize> {
}
#[hdl_module]
pub fn unit_base<MOp: Type + MOpTrait<DestReg = (), SrcRegWidth = DynSize>, ExtraOut: Type>(
config: PhantomConst<CpuConfig>,
pub fn unit_base<
MOp: Type + MOpTrait<DestReg = UnitOutRegNum<DynSize>, SrcRegWidth = DynSize>,
ExtraOut: Type,
>(
config: &CpuConfig,
unit_index: usize,
mop_ty: MOp,
extra_out_ty: ExtraOut,
@ -291,19 +251,18 @@ pub fn unit_base<MOp: Type + MOpTrait<DestReg = (), SrcRegWidth = DynSize>, Extr
#[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]);
let unit_to_reg_alloc: UnitToRegAlloc<MOp, ExtraOut, DynSize, DynSize, DynSize> =
m.output(config.unit_to_reg_alloc(mop_ty, extra_out_ty));
#[hdl]
let execute_start: ReadyValid<ExecuteStart<PhantomConst<CpuConfig>, MOp>> =
m.output(ReadyValid[ExecuteStart[config][mop_ty]]);
let execute_start: ReadyValid<ExecuteStart<MOp>> = m.output(ReadyValid[ExecuteStart[mop_ty]]);
#[hdl]
let execute_end: HdlOption<ExecuteEnd<PhantomConst<CpuConfig>, ExtraOut>> =
m.input(HdlOption[ExecuteEnd[config][extra_out_ty]]);
let execute_end: HdlOption<ExecuteEnd<DynSize, ExtraOut>> =
m.input(HdlOption[ExecuteEnd[config.out_reg_num_width][extra_out_ty]]);
connect(execute_start.data, Expr::ty(execute_start).data.HdlNone());
connect(execute_start.data, execute_start.ty().data.HdlNone());
let max_in_flight = config.get().unit_max_in_flight(unit_index).get();
let in_flight_op_ty = InFlightOp[config][mop_ty];
let max_in_flight = config.unit_max_in_flight(unit_index).get();
let in_flight_op_ty = InFlightOp[mop_ty];
#[hdl]
let in_flight_ops = reg_builder()
.clock_domain(cd)
@ -311,7 +270,7 @@ pub fn unit_base<MOp: Type + MOpTrait<DestReg = (), SrcRegWidth = DynSize>, Extr
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));
let in_flight_ops_summary = wire(in_flight_ops_summary_value.ty());
connect(in_flight_ops_summary, in_flight_ops_summary_value);
connect(
@ -319,21 +278,17 @@ pub fn unit_base<MOp: Type + MOpTrait<DestReg = (), SrcRegWidth = DynSize>, Extr
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::<_> {
let UnitForwardingInfo::<_, _, _> {
unit_output_writes,
unit_reg_frees,
_phantom: _,
} = 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),
repeat(config.p_reg_num().const_zero().cast_to_bits(), ConstUsize),
);
#[hdl]
let read_src_values = wire();
@ -342,33 +297,33 @@ pub fn unit_base<MOp: Type + MOpTrait<DestReg = (), SrcRegWidth = DynSize>, Extr
let input_src_regs = wire(mop_ty.src_regs_ty());
connect(
input_src_regs,
repeat(PRegNum[config].const_zero().cast_to_bits(), ConstUsize),
repeat(config.p_reg_num().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())
let mut unit_output_regs_valid: Vec<MemBuilder<Bool>> = (0..unit_output_writes.ty().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.depth(1 << config.out_reg_num_width);
mem
})
.collect();
for unit_index in 0..Expr::ty(unit_output_writes).len() {
for unit_index in 0..unit_output_writes.ty().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]);
unit_output_regs.depth(1 << config.out_reg_num_width);
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]);
let p_reg_num = read_src_regs[src_index].cast_bits_to(config.p_reg_num());
connect_any(read_port.addr, p_reg_num.unit_out_reg.value);
connect(read_port.en, false);
connect(read_port.clk, cd.clk);
@ -381,7 +336,7 @@ pub fn unit_base<MOp: Type + MOpTrait<DestReg = (), SrcRegWidth = DynSize>, Extr
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]);
let p_reg_num = input_src_regs[src_index].cast_bits_to(config.p_reg_num());
connect_any(read_port.addr, p_reg_num.unit_out_reg.value);
connect(read_port.en, false);
connect(read_port.clk, cd.clk);
@ -406,15 +361,15 @@ pub fn unit_base<MOp: Type + MOpTrait<DestReg = (), SrcRegWidth = DynSize>, Extr
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_any(write_port.addr, unit_output_write.which.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_any(ready_write_port.addr, unit_output_write.which.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,
PRegNum::<_, _> {
unit_num: config.unit_num().from_index(unit_index),
unit_out_reg: unit_output_write.which,
};
for src_index in 0..COMMON_MOP_SRC_LEN {
#[hdl]
@ -444,8 +399,9 @@ pub fn unit_base<MOp: Type + MOpTrait<DestReg = (), SrcRegWidth = DynSize>, Extr
execute_start.data,
HdlSome(
#[hdl]
ExecuteStart::<_, _> {
insn: in_flight_op.insn,
ExecuteStart::<_> {
mop: in_flight_op.mop,
pc: in_flight_op.pc,
src_values: read_src_values,
},
),
@ -455,7 +411,7 @@ pub fn unit_base<MOp: Type + MOpTrait<DestReg = (), SrcRegWidth = DynSize>, Extr
connect(
unit_to_reg_alloc.output,
Expr::ty(unit_to_reg_alloc.output).HdlNone(),
unit_to_reg_alloc.output.ty().HdlNone(),
);
#[hdl]
@ -464,17 +420,12 @@ pub fn unit_base<MOp: Type + MOpTrait<DestReg = (), SrcRegWidth = DynSize>, Extr
#[hdl]
if let HdlSome(input) = ReadyValid::firing_data(unit_to_reg_alloc.input) {
#[hdl]
let RenamedInsnData::<_, _, _> {
retire_queue_index,
pc: _,
dest: _,
mop,
} = input;
let UnitInput::<_> { mop, pc } = 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),
repeat(config.p_reg_num().const_zero().cast_to_bits(), ConstUsize),
);
MOp::connect_src_regs(mop, input_mop_src_regs);
let src_ready_flags = wire_with_loc(
@ -485,24 +436,20 @@ pub fn unit_base<MOp: Type + MOpTrait<DestReg = (), SrcRegWidth = DynSize>, Extr
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 {
if unit_to_reg_alloc.cancel_input.cmp_ne(HdlSome(
#[hdl]
UnitCancelInput::<_> {
which: MOp::dest_reg(mop),
},
)) {
connect(
input_in_flight_op,
HdlSome(
#[hdl]
InFlightOp::<_, _> {
InFlightOp::<_> {
state: InFlightOpState.Ready(),
insn: input,
mop,
pc,
src_ready_flags,
},
),
@ -536,11 +483,13 @@ pub fn unit_base<MOp: Type + MOpTrait<DestReg = (), SrcRegWidth = DynSize>, Extr
#[hdl]
if let HdlSome(in_flight_op) = in_flight_ops[in_flight_op_index] {
#[hdl]
let InFlightOp::<_, _> {
let InFlightOp::<_> {
state,
insn,
mop,
pc,
src_ready_flags,
} = in_flight_op;
let which = MOp::dest_reg(mop);
let src_regs = wire_with_loc(
&format!("in_flight_op_src_regs_{in_flight_op_index}"),
SourceLocation::caller(),
@ -548,13 +497,13 @@ pub fn unit_base<MOp: Type + MOpTrait<DestReg = (), SrcRegWidth = DynSize>, Extr
);
connect(
src_regs,
repeat(PRegNum[config].const_zero().cast_to_bits(), ConstUsize),
repeat(config.p_reg_num().const_zero().cast_to_bits(), ConstUsize),
);
MOp::connect_src_regs(insn.mop, src_regs);
MOp::connect_src_regs(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),
in_flight_op_index.cast_to(in_flight_ops_summary.ty().ready_op_index.HdlSome),
)) {
connect(read_src_regs, src_regs);
}
@ -563,17 +512,17 @@ pub fn unit_base<MOp: Type + MOpTrait<DestReg = (), SrcRegWidth = DynSize>, Extr
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() {
for unit_index in 0..unit_output_writes.ty().len() {
#[hdl]
if let HdlSome(unit_output_write) = unit_output_writes[unit_index] {
#[hdl]
let UnitOutputWrite::<_> {
dest: unit_out_reg,
which: unit_out_reg,
value: _,
} = unit_output_write;
let p_reg_num = #[hdl]
PRegNum::<_> {
unit_num: UnitNum[config].from_index(unit_index),
PRegNum::<_, _> {
unit_num: config.unit_num().from_index(unit_index),
unit_out_reg,
};
for src_index in 0..COMMON_MOP_SRC_LEN {
@ -588,21 +537,20 @@ pub fn unit_base<MOp: Type + MOpTrait<DestReg = (), SrcRegWidth = DynSize>, Extr
}
}
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),
);
}
connect(
in_flight_op_canceling[in_flight_op_index],
unit_to_reg_alloc.cancel_input.cmp_eq(HdlSome(
#[hdl]
UnitCancelInput::<_> { which },
)),
);
#[hdl]
if let HdlSome(execute_end) = execute_end {
#[hdl]
let ExecuteEnd::<_, _> { unit_output } = execute_end;
#[hdl]
if insn.dest.cmp_eq(unit_output.dest) {
if which.cmp_eq(unit_output.which) {
connect(in_flight_op_execute_ending[in_flight_op_index], true);
#[hdl]
if !in_flight_op_canceling[in_flight_op_index] {
@ -619,7 +567,7 @@ pub fn unit_base<MOp: Type + MOpTrait<DestReg = (), SrcRegWidth = DynSize>, Extr
#[hdl]
if let HdlSome(execute_start) = ReadyValid::firing_data(execute_start) {
#[hdl]
if insn.dest.cmp_eq(execute_start.insn.dest) {
if which.cmp_eq(MOp::dest_reg(execute_start.mop)) {
connect(in_flight_op_execute_starting[in_flight_op_index], true);
}
}
@ -646,9 +594,10 @@ pub fn unit_base<MOp: Type + MOpTrait<DestReg = (), SrcRegWidth = DynSize>, Extr
in_flight_ops[in_flight_op_index],
HdlSome(
#[hdl]
InFlightOp::<_, _> {
InFlightOp::<_> {
state,
insn,
mop,
pc,
src_ready_flags: in_flight_op_next_src_ready_flags[in_flight_op_index],
},
),

View file

@ -2,6 +2,7 @@
// See Notices.txt for copyright information
pub mod array_vec;
pub mod tree_reduce;
pub(crate) const fn range_u32_len(range: &std::ops::Range<u32>) -> usize {
let retval = range.end.saturating_sub(range.start);
@ -24,16 +25,3 @@ pub(crate) const fn range_u32_nth_or_panic(range: &std::ops::Range<u32>, index:
panic!("index out of range")
}
}
pub(crate) const fn range_intersection(
a: &std::ops::Range<u32>,
b: &std::ops::Range<u32>,
) -> Option<std::ops::Range<u32>> {
let start = if a.start > b.start { a.start } else { b.start };
let end = if a.end < b.end { a.end } else { b.end };
if start < end {
Some(start..end)
} else {
None
}
}

View file

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

View file

@ -0,0 +1,152 @@
// 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

1558
crates/cpu/tests/next_pc.rs Normal file

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -31,6 +31,7 @@ function check_file()
POUND_HEADER=('^"# SPDX-License-Identifier: LGPL-3.0-or-later"$' '^"# See Notices.txt for copyright information"$')
SLASH_HEADER=('^"// SPDX-License-Identifier: LGPL-3.0-or-later"$' '^"// See Notices.txt for copyright information"$')
MD_HEADER=('^"<!--"$' '^"SPDX-License-Identifier: LGPL-3.0-or-later"$' '^"See Notices.txt for copyright information"$')
MERMAID_HEADER=('^"%% SPDX-License-Identifier: LGPL-3.0-or-later"$' '^"%% See Notices.txt for copyright information"$')
function main()
{
@ -51,6 +52,9 @@ function main()
/.forgejo/workflows/*.yml|*/.gitignore|*.toml)
check_file "$file" "${POUND_HEADER[@]}"
;;
*.mermaid)
check_file "$file" "${MERMAID_HEADER[@]}"
;;
*.md)
check_file "$file" "${MD_HEADER[@]}"
;;