Compare commits

...
Sign in to create a new pull request.

119 commits

Author SHA1 Message Date
7acfaebfde
unit::alu_branch::compare: implement CmpRBOne/CmpRBTwo/CmpEqB 2026-05-28 19:12:41 -07:00
7481d079d5
redo ShiftRotateMOp and write actual operation definition in doc comment 2026-05-28 01:41:31 -07:00
a3898e8c13
unit::alu_branch: convert *MOp functions to hdl_module 2026-05-26 20:10:41 -07:00
42056ce6b5
unit::alu_branch: implement for LogicalMOp 2026-05-26 19:05:19 -07:00
b25448a275
unit::alu_branch: implement for LogicalFlagsMOp 2026-05-25 23:42:57 -07:00
99c019431b
unit::alu_branch: implement for ShiftRotateMOp; TODO: flags and shift amount overflow 2026-05-25 23:15:43 -07:00
dd2fe36869
rename_execute_retire: add more tests using unit::alu_branch 2026-05-25 21:31:43 -07:00
225ceb0dfa
unit::alu_branch: implement for CompareMOp 2026-05-25 18:49:04 -07:00
67abfa2f5d
unit::alu_branch::add_sub: x86 CF and AF are used as borrow flags for subtraction 2026-05-25 18:48:59 -07:00
5558763718
rename test_rename_execute_retire_real_alu_branch -> test_rename_execute_retire_save_restore_gprs_real 2026-05-25 18:35:59 -07:00
7151841af5
make Unit API work with rename_execute_retire and add a rename_execute_retire test using unit::alu_branch 2026-05-24 22:23:23 -07:00
a88009a303
add GlobalState to ExecuteToUnitInterface 2026-05-24 19:56:14 -07:00
ce8519b2db
util: add and use checked_vcd_output 2026-05-24 19:49:10 -07:00
151683fbda
rename_execute_retire: add reference counting for L1 registers 2026-05-21 23:41:11 -07:00
e0dc5d486b
rename_execute_retire: add reference counting for L2 registers 2026-05-21 21:06:40 -07:00
fdf1e97e10
move RenameTable and ReorderBuffer into their own mods 2026-05-21 20:46:19 -07:00
bf2cb688c7
implement register fences and use for L2 reg file writes and when running out of L2 reg file output regs
fixes deadlock when running rename_execute_retire_save_restore_gprs
2026-05-21 17:23:57 -07:00
3e08a282ec
add test_rename_execute_retire_save_restore_gprs
currently it fails due to the L2 reg file running out of output registers
2026-05-20 19:44:20 -07:00
6026df8d7a
rename_execute_retire: generate l2 stores earlier to make more space in units to increase throughput 2026-05-20 17:02:34 -07:00
e502dfe574
rename_execute_retire: don't include completed instructions in space used by a unit 2026-05-20 16:56:58 -07:00
0d69666b00
tests/rename_execute_retire: add and use mock_combinational_unit 2026-05-19 19:33:09 -07:00
2363e65564
tests/rename_execute_retire: make loads/stores take more than one cycle to execute 2026-05-19 18:08:07 -07:00
79ac190093
rename_execute_retire: add a head -n1 test 2026-05-18 22:22:31 -07:00
0d3c41fa14
add TraceAsString around instructions and stuff to make the .vcd files much smaller and easier to read 2026-05-14 22:38:50 -07:00
8bee576a2a
update fayalite to get TraceAsString 2026-05-14 22:28:25 -07:00
3fbdab0862
rename_execute_retire: implement generating L2 reg file writes 2026-05-10 23:39:02 -07:00
33b5d59507
improve debug formatting of PRegValue and PRegFlags 2026-05-07 21:40:23 -07:00
559e2967a2
improve debug formatting of MOpRegNum/MOpDestReg 2026-05-07 21:25:27 -07:00
5e6041a97c
change MOp to use SrcReg: Type instead of UIntType<SrcRegWidth> 2026-05-07 19:56:56 -07:00
409ca7bf97
update decode_one_insn.vcd for modified instruction data structures 2026-05-05 21:52:02 -07:00
9308e5d195
update fayalite to fix bug in VCD generation 2026-05-05 21:28:46 -07:00
09c8c194e0
group micro ops by the instruction they come from when retiring 2026-05-05 19:33:25 -07:00
83b3f7bac9
use custom debug 2026-05-03 23:35:19 -07:00
ba9ec3bd29
adapt code for new fayalite features 2026-05-03 23:35:19 -07:00
1229d9c758
update fayalite for optimizations and new features 2026-05-03 23:35:19 -07:00
283117d8df
add support for speculative loads 2026-04-30 17:51:33 -07:00
4d21ca622b
add initial impl of rename_execute_retire; running a recursive fibonacci gives the correct output 2026-04-24 18:13:27 -07:00
6ed04c809e
update fayalite for libre-chip/fayalite#68 change vcd output to have module contents under instance's name 2026-03-26 19:21:52 -07:00
a1147f0f05
add sram and main_memory_and_io modules 2026-03-26 02:03:06 -07:00
5bdc71acc3
add memory_interface_adapter_no_split 2026-03-26 02:03:06 -07:00
a15367c37e
add address_range to MemoryInterfaceConfig and add support to simple_uart 2026-03-24 23:46:46 -07:00
0d451e4e95
change MemoryInterface types to have their own config 2026-03-24 23:46:46 -07:00
689b4ef65a
implement simple_uart::simple_uart 2026-03-24 23:46:46 -07:00
79eac8929a
add test for simple_uart::receiver 2026-03-24 23:46:46 -07:00
f372190b68
add main_memory_and_io::simple_uart::receiver* and test for receiver_no_queue 2026-03-24 23:46:46 -07:00
e31375b9ce
add main_memory_and_io::simple_uart::{transmitter, uart_clock_gen} and tests 2026-03-24 23:46:46 -07:00
4ffaba8840
add MemoryOperationStart.rw_mask 2026-03-24 23:46:46 -07:00
4de29dd16e
move MemoryInterface and related types to crate::main_memory_and_io 2026-03-24 23:46:46 -07:00
245b872cf7
update to latest fayalite 2026-03-24 23:46:46 -07:00
e69c92c8da
add fetch::fetch and fetch::l1_i_cache with some testing 2026-02-21 18:32:01 -08:00
c62d33048c
update fayalite to c632e5d570 to speed up simulation 2026-02-04 16:33:46 -08:00
596440755c
update fayalite to include 1bc835803b for a major speedup of the decoder tests 2026-02-03 18:17:31 -08:00
68a4373bbd
update rust version to 1.93.0 2026-02-03 18:17:18 -08:00
f88346ea37
implement decoding mtspr/mfspr/mftb 2026-01-28 17:35:09 -08:00
a42b76b468
implement decoding all rotate instructions 2026-01-27 19:18:27 -08:00
130c1b2892
change CommonMOp to directly contain a generic immediate type 2026-01-27 17:03:14 -08:00
167bc4b6a6
implement decoding extswsli[.] 2026-01-26 19:18:44 -08:00
faa8dde774
reduce the number of wires to have one per form/field pair instead of one per insn/field pair 2026-01-26 16:11:03 -08:00
1db65ae753
implement decoding shifts: s[lr][wd][.] and sra[wd][i][.] 2026-01-26 15:14:26 -08:00
59874b9b29
add shift/rotate MOp definition 2026-01-25 20:34:38 -08:00
2ad469e331
simplify getting IMM_WIDTH for LogicalFlagsMOpImm 2026-01-25 20:25:25 -08:00
0824b63d31
implement decoding 8/16/32/64-bit store instructions -- all of Power ISA v3.1c Book I 3.3.3 2026-01-25 15:06:14 -08:00
706d54ae0d
implement decoding 8/16/32/64-bit load instructions -- all of Power ISA v3.1C Book I 3.3.2 2026-01-23 16:06:16 -08:00
d361a2b578
make LogicalFlagsMOp also copy the dest PRegValue.flags into PRegValue.int_fp 2026-01-23 12:13:06 -08:00
aa07e24c78
make check-copyright.sh also handle other tests/.../expected/... files 2026-01-23 12:13:06 -08:00
29757a568c
implement decoding mcrf 2026-01-23 11:47:06 -08:00
33529a2296
implement decoding condition register logical instructions 2026-01-23 11:27:48 -08:00
5e9d0957f6
reorder the decoder test cases to match the PowerISA v3.1C PDF 2026-01-23 09:46:29 -08:00
fc8a6cd959
split up tests/simple_power_isa_decoder into separate modules 2026-01-23 09:38:45 -08:00
87112c681a
move simple_power_isa_decoder integration test into its own folder 2026-01-23 08:46:28 -08:00
e6f876f9af
fix & clean up MOp definitions and ensure_reg_fields_are_in_the_same_place 2026-01-22 08:28:02 -08:00
9b8d99e9af
implement decoding mcrxrx 2026-01-22 07:34:53 -08:00
ffc3d4283c
refactor PRegFlags to use view structs instead of a long list of accessor methods 2026-01-20 19:07:46 -08:00
0433e4f8f1
mark .vcd files as generated 2026-01-20 16:20:40 -08:00
a93dca25ac
extract lut out into separate Lut4 type and add test 2026-01-20 16:03:28 -08:00
85ada6e55a
add tests for and fix decoding branch instructions 2026-01-19 22:38:48 -08:00
2e05329c36
add branch instructions, no tests yet 2026-01-19 20:09:21 -08:00
1fc56e02f9
decode exts[bhw][.] and pnop 2026-01-19 15:41:56 -08:00
aa85ecab01
simplify tests/simple_power_isa_decoder::test_cases somewhat 2026-01-19 14:16:00 -08:00
62a330ed4d
update reg_alloc.vcd 2026-01-18 23:23:54 -08:00
c9a3de19b7
add test that UnitMOp has all the register fields aligned across the different variants 2026-01-18 22:50:38 -08:00
7ebcd5de1e
decode bitwise logic instructions; also nop and mr special cases 2026-01-18 19:12:48 -08:00
3a35a698e2
decode fixed-point compare instructions 2026-01-18 16:37:00 -08:00
a4b052f5f3
decode all fixed-point add/sub instructions other than addex 2026-01-18 15:02:15 -08:00
62512960c3
decode some more add instructions 2026-01-15 16:10:03 -08:00
b7b6a02777
decodes an addi instruction 2026-01-14 21:41:25 -08:00
6d40eaadb3
WIP adding simple power isa decoder 2026-01-12 07:10:58 -08:00
305d7b0ae6
rename src/powerisa.rs -> src/powerisa_instructions_xml.rs 2026-01-12 03:31:06 -08:00
781dbc6bcb
add copyright header to build.rs 2026-01-09 02:05:08 -08:00
5faf0899a8
fully parse powerisa-instructions.xml 2026-01-09 02:03:59 -08:00
8c02483b65
wip parsing the xml 2026-01-08 00:09:20 -08:00
e4a7d9f59c
add build script that parses the PowerISA v3.1C pdf and produces an xml file 2026-01-07 14:19:46 -08:00
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
74 changed files with 2279614 additions and 34812 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,56 +3,18 @@
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' }}
- run: rustup override set 1.93.0
- run: cargo test

3
.gitattributes vendored Normal file
View file

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

1
.gitignore vendored
View file

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

617
Cargo.lock generated
View file

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

View file

@ -7,14 +7,23 @@ 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.93.0"
[workspace.dependencies]
base16ct = "1.0.0"
fayalite = { git = "https://git.libre-chip.org/libre-chip/fayalite.git", version = "0.3.0", branch = "master" }
hex-literal = "1.1.0"
parse_powerisa_pdf = { git = "https://git.libre-chip.org/libre-chip/parse_powerisa_pdf.git", version = "0.1.0", branch = "master" }
roxmltree = "0.21.1"
serde = { version = "1.0.202", features = ["derive"] }
sha2 = "0.10.9"
simple-mermaid = "0.2.0"
ureq = "3.1.4"
which = { version = "6.0.3", features = ["regex"] }
[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

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

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

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

96
crates/cpu/build.rs Normal file
View file

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

View file

@ -1,16 +1,11 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
instruction::{MOpTrait, PRegNum, RenamedMOp, UnitNum, UnitOutRegNum, CONST_ZERO_UNIT_NUM},
unit::{
unit_base::{UnitForwardingInfo, UnitToRegAlloc},
UnitCancelInput, UnitKind, UnitOutputWrite,
},
};
use fayalite::prelude::*;
use crate::{instruction::CONST_ZERO_UNIT_NUM, unit::UnitKind};
use fayalite::{expr::HdlPartialOrdImpl, prelude::*};
use serde::{Deserialize, Serialize};
use std::num::NonZeroUsize;
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
#[derive(Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct UnitConfig {
pub kind: UnitKind,
@ -27,12 +22,18 @@ impl UnitConfig {
}
}
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
#[derive(Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct CpuConfig {
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,
pub log2_cache_line_size_in_bytes: u8,
pub log2_l1_i_cache_line_count: u8,
pub l1_i_cache_max_misses_in_flight: NonZeroUsize,
/// default value for [`UnitConfig::max_in_flight`]
pub default_unit_max_in_flight: NonZeroUsize,
pub rob_size: NonZeroUsize,
@ -46,6 +47,27 @@ impl CpuConfig {
};
v
};
pub const DEFAULT_MAX_BRANCHES_PER_FETCH: NonZeroUsize = {
let Some(v) = NonZeroUsize::new(1) else {
unreachable!();
};
v
};
pub const DEFAULT_MAX_FETCHES_IN_FLIGHT: NonZeroUsize = {
let Some(v) = NonZeroUsize::new(16) else {
unreachable!();
};
v
};
pub const DEFAULT_LOG2_FETCH_WIDTH_IN_BYTES: u8 = 3;
pub const DEFAULT_LOG2_CACHE_LINE_SIZE_IN_BYTES: u8 = 6;
pub const DEFAULT_LOG2_L1_I_CACHE_LINE_COUNT: u8 = 8;
pub const DEFAULT_L1_I_CACHE_MAX_MISSES_IN_FLIGHT: NonZeroUsize = {
let Some(v) = NonZeroUsize::new(2) else {
unreachable!();
};
v
};
pub const DEFAULT_UNIT_MAX_IN_FLIGHT: NonZeroUsize = {
let Some(v) = NonZeroUsize::new(8) else {
unreachable!();
@ -57,6 +79,12 @@ impl CpuConfig {
units,
out_reg_num_width: Self::DEFAULT_OUT_REG_NUM_WIDTH,
fetch_width: Self::DEFAULT_FETCH_WIDTH,
max_branches_per_fetch: Self::DEFAULT_MAX_BRANCHES_PER_FETCH,
max_fetches_in_flight: Self::DEFAULT_MAX_FETCHES_IN_FLIGHT,
log2_fetch_width_in_bytes: Self::DEFAULT_LOG2_FETCH_WIDTH_IN_BYTES,
log2_cache_line_size_in_bytes: Self::DEFAULT_LOG2_CACHE_LINE_SIZE_IN_BYTES,
log2_l1_i_cache_line_count: Self::DEFAULT_LOG2_L1_I_CACHE_LINE_COUNT,
l1_i_cache_max_misses_in_flight: Self::DEFAULT_L1_I_CACHE_MAX_MISSES_IN_FLIGHT,
default_unit_max_in_flight: Self::DEFAULT_UNIT_MAX_IN_FLIGHT,
rob_size,
}
@ -67,53 +95,135 @@ 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 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()]
/// the maximum of all [`unit_max_in_flight()`][Self::unit_max_in_flight()]
pub fn max_unit_max_in_flight(&self) -> NonZeroUsize {
(0..self.units.len())
.map(|unit_index| self.unit_max_in_flight(unit_index))
.max()
.unwrap_or(self.default_unit_max_in_flight)
}
pub fn fetch_width_in_bytes(&self) -> usize {
1usize
.checked_shl(self.log2_fetch_width_in_bytes.into())
.expect("log2_fetch_width_in_bytes is too big")
}
pub fn cache_line_size_in_bytes(&self) -> usize {
1usize
.checked_shl(self.log2_cache_line_size_in_bytes.into())
.expect("log2_cache_line_size_in_bytes is too big")
}
pub fn log2_fetches_per_cache_line(&self) -> usize {
self.log2_cache_line_size_in_bytes
.checked_sub(self.log2_fetch_width_in_bytes)
.expect("cache line size in bytes must not be smaller than fetch width in bytes")
.into()
}
pub fn fetches_per_cache_line(&self) -> usize {
self.log2_fetches_per_cache_line()
.try_into()
.ok()
.and_then(|v| 1usize.checked_shl(v))
.expect("log2_fetches_per_cache_line is too big")
}
pub fn l1_i_cache_line_count(&self) -> usize {
1usize
.checked_shl(self.log2_l1_i_cache_line_count.into())
.expect("log2_l1_i_cache_line_count is too big")
}
pub fn log2_l1_i_cache_size_in_bytes(&self) -> usize {
self.log2_l1_i_cache_line_count as usize + self.log2_cache_line_size_in_bytes as usize
}
pub fn l1_i_cache_size_in_bytes(&self) -> usize {
1usize
.checked_shl(self.log2_l1_i_cache_size_in_bytes() as _)
.expect("L1 I-Cache is too big")
}
}
#[hdl(get(|c| c.out_reg_num_width))]
pub type CpuConfigOutRegNumWidth<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.unit_num_width()))]
pub type CpuConfigUnitNumWidth<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.p_reg_num_width()))]
pub type CpuConfigPRegNumWidth<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| 1 << c.out_reg_num_width))]
pub type CpuConfig2PowOutRegNumWidth<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.units.len()))]
pub type CpuConfigUnitCount<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.fetch_width.get()))]
pub type CpuConfigFetchWidth<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.fetch_width.get() * 2))]
pub type TwiceCpuConfigFetchWidth<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.max_branches_per_fetch.get()))]
pub type CpuConfigMaxBranchesPerFetch<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.max_fetches_in_flight.get()))]
pub type CpuConfigMaxFetchesInFlight<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.log2_fetch_width_in_bytes.into()))]
pub type CpuConfigLog2FetchWidthInBytes<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.fetch_width_in_bytes()))]
pub type CpuConfigFetchWidthInBytes<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.log2_fetches_per_cache_line()))]
pub type CpuConfigLog2FetchesPerCacheLine<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.fetches_per_cache_line()))]
pub type CpuConfigFetchesPerCacheLine<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.log2_cache_line_size_in_bytes.into()))]
pub type CpuConfigLog2CacheLineSizeInBytes<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.cache_line_size_in_bytes()))]
pub type CpuConfigCacheLineSizeInBytes<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.log2_l1_i_cache_line_count.into()))]
pub type CpuConfigLog2L1ICacheLineCount<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.l1_i_cache_line_count()))]
pub type CpuConfigL1ICacheLineCount<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.log2_l1_i_cache_size_in_bytes()))]
pub type CpuConfigLog2L1ICacheSizeInBytes<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.l1_i_cache_size_in_bytes()))]
pub type CpuConfigL1ICacheSizeInBytes<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.l1_i_cache_max_misses_in_flight.get()))]
pub type CpuConfigL1ICacheMaxMissesInFlight<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(get(|c| c.rob_size.get()))]
pub type CpuConfigRobSize<C: PhantomConstGet<CpuConfig>> = DynSize;
/// the maximum of all [`unit_max_in_flight()`][CpuConfig::unit_max_in_flight()]
#[hdl(get(|c| c.max_unit_max_in_flight().get()))]
pub type CpuConfigMaxUnitMaxInFlight<C: PhantomConstGet<CpuConfig>> = DynSize;
pub trait PhantomConstCpuConfig:
PhantomConstGet<CpuConfig>
+ Into<PhantomConst<CpuConfig>>
+ From<PhantomConst<CpuConfig>>
+ Type
+ ToSimValue<Type = Self>
+ ToExpr<Type = Self>
+ HdlPartialOrdImpl<Self>
{
}
impl PhantomConstCpuConfig for PhantomConst<CpuConfig> {}

View file

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

File diff suppressed because it is too large Load diff

1526
crates/cpu/src/fetch.rs Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

4671
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

File diff suppressed because it is too large Load diff

View file

@ -3,18 +3,18 @@
use crate::{
config::CpuConfig,
instruction::{
MOp, MOpDestReg, MOpRegNum, MOpTrait, MoveRegMOp, PRegNum, RenameTableName, UnitOutRegNum,
COMMON_MOP_SRC_LEN,
COMMON_MOP_SRC_LEN, MOp, MOpDestReg, MOpRegNum, MOpTrait, MoveRegMOp, PRegNum,
RenameTableName, UnitOutRegNum,
},
unit::{
unit_base::{UnitForwardingInfo, UnitInput},
GlobalState, TrapData, UnitMOp, UnitOutput, UnitOutputWrite, UnitResult,
UnitResultCompleted, UnitTrait,
unit_base::{UnitForwardingInfo, UnitInput},
},
util::tree_reduce::tree_reduce_with_state,
};
use fayalite::{
memory::{splat_mask, WriteStruct},
memory::{WriteStruct, splat_mask},
module::{instance_with_loc, memory_with_loc, wire_with_loc},
prelude::*,
util::ready_valid::ReadyValid,
@ -241,7 +241,7 @@ pub fn reg_alloc(config: &CpuConfig) {
// TODO: finish
connect(
rob.renamed_insns_in[fetch_index].data,
Expr::ty(rob).renamed_insns_in.element().data.HdlNone(),
rob.ty().renamed_insns_in.element().data.HdlNone(),
);
// TODO: finish
connect(
@ -263,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> {
@ -332,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);
@ -343,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()),
},
);
@ -375,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(
@ -429,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}"),
@ -447,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);
@ -516,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]
@ -550,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(), "");
}
@ -578,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

@ -73,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]
@ -120,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(
@ -198,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()]);

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

@ -4,39 +4,39 @@
use crate::{
config::CpuConfig,
instruction::{
mop_enum, AluBranchMOp, LoadStoreMOp, MOp, MOpDestReg, MOpInto, MOpRegNum, MOpTrait,
RenamedMOp, UnitOutRegNum,
AluBranchMOp, LoadStoreMOp, MOp, MOpDestReg, MOpInto, MOpRegNum, MOpTrait,
MOpVariantVisitOps, MOpVariantVisitor, MOpVisitVariants, RenamedMOp, mop_enum,
},
register::{FlagsMode, PRegValue},
unit::unit_base::UnitToRegAlloc,
rename_execute_retire::ExecuteToUnitInterface,
};
use fayalite::{
bundle::{Bundle, BundleType},
intern::{Intern, Interned},
prelude::*,
};
use serde::{Deserialize, Serialize};
use std::ops::ControlFlow;
pub mod alu_branch;
pub mod unit_base;
macro_rules! all_units {
(
#[hdl_unit_kind = $HdlUnitKind:ident]
#[unit_kind = $UnitKind:ident]
#[hdl]
#[hdl(custom_debug(sim))]
$(#[$enum_meta:meta])*
$vis:vis enum $UnitMOpEnum:ident<$DestReg:ident: Type, $SrcRegWidth:ident: Size, #[MOp(get_ty = $transformed_move_op_get_ty:expr)] $TransformedMoveOp:ident: Type> {
$vis:vis enum $UnitMOpEnum:ident<$DestReg:ident: Type, $SrcReg:ident: Type, #[MOp(get_ty = $transformed_move_op_get_ty:expr)] $TransformedMoveOp:ident: Type> {
$(
$(#[transformed_move $($transformed_move:tt)*])?
#[create_dyn_unit_fn = $create_dyn_unit_fn:expr]
#[extract = $extract:ident]
#[extract($extract:ident, $extract_sim:ident, $extract_sim_ref:ident, $extract_sim_mut:ident)]
$(#[$variant_meta:meta])*
$Unit:ident($Op:ty),
)*
}
) => {
$(#[$enum_meta])*
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)]
$vis enum $UnitKind {
$(
$(#[$variant_meta])*
@ -45,16 +45,23 @@ macro_rules! all_units {
}
impl $UnitKind {
pub fn unit(self, config: &CpuConfig, unit_index: usize) -> DynUnit {
pub fn unit(self, config: PhantomConst<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(),)*
@ -73,9 +80,17 @@ macro_rules! all_units {
mop_enum! {
#[impl_mop_into = false]
#[hdl]
#[hdl(custom_debug(sim))]
$(#[$enum_meta])*
$vis enum $UnitMOpEnum<$DestReg: Type, $SrcRegWidth: Size, #[MOp(get_ty = $transformed_move_op_get_ty)] $TransformedMoveOp: Type> {
$vis enum $UnitMOpEnum<
$DestReg: Type,
$SrcReg: Type,
#[MOp(get_ty = $transformed_move_op_get_ty)] $TransformedMoveOp: Type,
#[MOpVisitVariants] [
$TransformedMoveOp: MOpVisitVariants<DestReg = $DestReg, SrcReg = $SrcReg>,
$($Op: MOpVisitVariants<DestReg = $DestReg, SrcReg = $SrcReg>,)*
]
> {
$(
$(#[$variant_meta])*
$Unit($Op),
@ -83,7 +98,7 @@ macro_rules! all_units {
}
}
impl<$DestReg: Type, $SrcRegWidth: Size, $TransformedMoveOp: Type> $UnitMOpEnum<$DestReg, $SrcRegWidth, $TransformedMoveOp> {
impl<$DestReg: Type, $SrcReg: Type, $TransformedMoveOp: Type> $UnitMOpEnum<$DestReg, $SrcReg, $TransformedMoveOp> {
#[hdl]
$vis fn kind(expr: impl ToExpr<Type = Self>) -> Expr<$HdlUnitKind> {
#[hdl]
@ -94,11 +109,20 @@ macro_rules! all_units {
}
unit_kind
}
#[hdl]
$vis fn kind_sim(expr: &SimValue<Self>) -> UnitKind {
#![allow(unreachable_patterns)]
#[hdl(sim)]
match expr {
$(Self::$Unit(_) => $UnitKind::$Unit,)*
_ => unreachable!(),
}
}
$(
#[hdl]
$vis fn $extract(expr: impl ToExpr<Type = Self>) -> Expr<HdlOption<$Op>> {
let expr = expr.to_expr();
let ty = Expr::ty(expr);
let ty = expr.ty();
#[hdl]
let $extract = wire(HdlOption[ty.$Unit]);
connect($extract, HdlOption[ty.$Unit].HdlNone());
@ -108,19 +132,47 @@ macro_rules! all_units {
}
$extract
}
#[hdl]
$vis fn $extract_sim(expr: impl ToSimValue<Type = Self>) -> Option<SimValue<$Op>> {
let expr = expr.into_sim_value();
#[hdl(sim)]
if let Self::$Unit(v) = expr {
Some(v)
} else {
None
}
}
#[hdl]
$vis fn $extract_sim_ref(expr: &SimValue<Self>) -> Option<&SimValue<$Op>> {
#[hdl(sim)]
if let Self::$Unit(v) = expr {
Some(v)
} else {
None
}
}
#[hdl]
$vis fn $extract_sim_mut(expr: &mut SimValue<Self>) -> Option<&mut SimValue<$Op>> {
#[hdl(sim)]
if let Self::$Unit(v) = expr {
Some(v)
} else {
None
}
}
)*
$vis fn with_transformed_move_op_ty<T>(self, new_transformed_move_op_ty: T) -> $UnitMOpEnum<$DestReg, $SrcRegWidth, T>
$vis fn with_transformed_move_op_ty<T>(self, new_transformed_move_op_ty: T) -> $UnitMOpEnum<$DestReg, $SrcReg, T>
where
T: MOpTrait<DestReg = $DestReg, SrcRegWidth = $SrcRegWidth>,
$TransformedMoveOp: MOpTrait<DestReg = $DestReg, SrcRegWidth = $SrcRegWidth>,
T: MOpTrait<DestReg = $DestReg, SrcReg = $SrcReg>,
$TransformedMoveOp: MOpTrait<DestReg = $DestReg, SrcReg = $SrcReg>,
{
$UnitMOpEnum[self.dest_reg_ty()][self.src_reg_width()][new_transformed_move_op_ty]
$UnitMOpEnum[self.dest_reg_ty()][self.src_reg_ty()][new_transformed_move_op_ty]
}
}
all_units! {
@split_by_transformed_move
$vis enum $UnitMOpEnum<$DestReg: Type, $SrcRegWidth: Size, $TransformedMoveOp: Type> {
$vis enum $UnitMOpEnum<$DestReg: Type, $SrcReg: Type, $TransformedMoveOp: Type> {
$(
$(#[transformed_move $($transformed_move)*])?
$Unit($Op),
@ -145,29 +197,29 @@ macro_rules! all_units {
};
(
@split_by_transformed_move
$vis:vis enum $UnitMOpEnum:ident<$DestReg:ident: Type, $SrcRegWidth:ident: Size, $TransformedMoveOp:ident: Type> {
$vis:vis enum $UnitMOpEnum:ident<$DestReg:ident: Type, $SrcReg:ident: Type, $TransformedMoveOp:ident: Type> {
$($BeforeUnit:ident($BeforeOp:ty),)*
#[transformed_move]
$TransformedMove:ident($TransformedMoveOp2:ty),
$($AfterUnit:ident($AfterOp:ty),)*
}
) => {
impl<$DestReg: Type, $SrcRegWidth: Size, $TransformedMoveOp: Type> $UnitMOpEnum<$DestReg, $SrcRegWidth, $TransformedMoveOp> {
impl<$DestReg: Type, $SrcReg: Type, $TransformedMoveOp: Type> $UnitMOpEnum<$DestReg, $SrcReg, $TransformedMoveOp> {
#[hdl]
$vis fn try_with_transformed_move_op<T>(
this: impl ToExpr<Type = Self>,
new_transformed_move_op_ty: T,
connect_transformed_move_op: impl FnOnce(Expr<HdlOption<$UnitMOpEnum<$DestReg, $SrcRegWidth, T>>>, Expr<$TransformedMoveOp>),
) -> Expr<HdlOption<$UnitMOpEnum<$DestReg, $SrcRegWidth, T>>>
connect_transformed_move_op: impl FnOnce(Expr<HdlOption<$UnitMOpEnum<$DestReg, $SrcReg, T>>>, Expr<$TransformedMoveOp>),
) -> Expr<HdlOption<$UnitMOpEnum<$DestReg, $SrcReg, T>>>
where
T: MOpTrait<DestReg = $DestReg, SrcRegWidth = $SrcRegWidth>,
$TransformedMoveOp: MOpTrait<DestReg = $DestReg, SrcRegWidth = $SrcRegWidth>,
T: MOpTrait<DestReg = $DestReg, SrcReg = $SrcReg>,
$TransformedMoveOp: MOpTrait<DestReg = $DestReg, SrcReg = $SrcReg>,
{
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]
@ -178,12 +230,51 @@ macro_rules! all_units {
}
with_transformed_move_op
}
#[hdl]
$vis fn try_with_transformed_move_op_sim<T, E>(
this: impl ToSimValue<Type = Self>,
new_transformed_move_op_ty: T,
f: impl FnOnce(SimValue<$TransformedMoveOp>) -> Result<SimValue<$UnitMOpEnum<$DestReg, $SrcReg, T>>, E>,
) -> Result<SimValue<$UnitMOpEnum<$DestReg, $SrcReg, T>>, E>
where
T: MOpTrait<DestReg = $DestReg, SrcReg = $SrcReg>,
$TransformedMoveOp: MOpTrait<DestReg = $DestReg, SrcReg = $SrcReg>,
{
#![allow(unreachable_patterns)]
let this = this.into_sim_value();
let new_ty = this.ty().with_transformed_move_op_ty(new_transformed_move_op_ty);
#[hdl(sim)]
match this {
$(Self::$BeforeUnit(unit) => Ok(
#[hdl(sim)]
new_ty.$BeforeUnit(unit)
),)*
Self::$TransformedMove(unit) => f(unit),
$(Self::$AfterUnit(unit) => Ok(
#[hdl(sim)]
new_ty.$AfterUnit(unit)
),)*
_ => unreachable!(),
}
}
$vis fn with_transformed_move_op_sim<T>(
this: impl ToSimValue<Type = Self>,
new_transformed_move_op_ty: T,
f: impl FnOnce(SimValue<$TransformedMoveOp>) -> SimValue<$UnitMOpEnum<$DestReg, $SrcReg, T>>,
) -> SimValue<$UnitMOpEnum<$DestReg, $SrcReg, T>>
where
T: MOpTrait<DestReg = $DestReg, SrcReg = $SrcReg>,
$TransformedMoveOp: MOpTrait<DestReg = $DestReg, SrcReg = $SrcReg>,
{
let Ok::<_, std::convert::Infallible>(retval) = Self::try_with_transformed_move_op_sim(this, new_transformed_move_op_ty, move |v| Ok(f(v)));
retval
}
}
const _: () = {
#[hdl]
type $DestReg = MOpDestReg;
type $SrcRegWidth = ConstUsize<{ MOpRegNum::WIDTH }>;
type $SrcReg = MOpRegNum;
$(impl MOpInto<MOp> for $BeforeOp {
fn mop_into_ty(self) -> MOp {
@ -204,21 +295,21 @@ macro_rules! all_units {
})*
};
$(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)]
$(impl<$DestReg: Type, $SrcReg: Type> MOpInto<RenamedMOp<$DestReg, $SrcReg>> for $BeforeOp {
fn mop_into_ty(self) -> RenamedMOp<$DestReg, $SrcReg> {
RenamedMOp[MOpTrait::dest_reg_ty(self)][MOpTrait::src_reg_ty(self)]
}
fn mop_into(this: Expr<Self>) -> Expr<RenamedMOp<$DestReg, $SrcRegWidth>> {
MOpInto::<RenamedMOp<$DestReg, $SrcRegWidth>>::mop_into_ty(Expr::ty(this)).$BeforeUnit(this)
fn mop_into(this: Expr<Self>) -> Expr<RenamedMOp<$DestReg, $SrcReg>> {
MOpInto::<RenamedMOp<$DestReg, $SrcReg>>::mop_into_ty(this.ty()).$BeforeUnit(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)]
$(impl<$DestReg: Type, $SrcReg: Type> MOpInto<RenamedMOp<$DestReg, $SrcReg>> for $AfterOp {
fn mop_into_ty(self) -> RenamedMOp<$DestReg, $SrcReg> {
RenamedMOp[MOpTrait::dest_reg_ty(self)][MOpTrait::src_reg_ty(self)]
}
fn mop_into(this: Expr<Self>) -> Expr<RenamedMOp<$DestReg, $SrcRegWidth>> {
MOpInto::<RenamedMOp<$DestReg, $SrcRegWidth>>::mop_into_ty(Expr::ty(this)).$AfterUnit(this)
fn mop_into(this: Expr<Self>) -> Expr<RenamedMOp<$DestReg, $SrcReg>> {
MOpInto::<RenamedMOp<$DestReg, $SrcReg>>::mop_into_ty(this.ty()).$AfterUnit(this)
}
})*
};
@ -227,111 +318,44 @@ macro_rules! all_units {
all_units! {
#[hdl_unit_kind = HdlUnitKind]
#[unit_kind = UnitKind]
#[hdl]
#[hdl(custom_debug(sim))]
pub enum UnitMOp<
DestReg: Type,
SrcRegWidth: Size,
#[MOp(get_ty = |this: UnitMOp<DestReg, SrcRegWidth, TransformedMoveOp>, new_dest_reg, new_src_reg_width| {
this.TransformedMove.mapped_ty(new_dest_reg, new_src_reg_width)
SrcReg: Type,
#[MOp(get_ty = |this: UnitMOp<DestReg, SrcReg, TransformedMoveOp>, new_dest_reg, new_src_reg| {
this.TransformedMove.mapped_ty(new_dest_reg, new_src_reg)
})] TransformedMoveOp: Type
> {
#[create_dyn_unit_fn = |config, unit_index| alu_branch::AluBranch::new(config, unit_index).to_dyn()]
#[extract = alu_branch_mop]
AluBranch(AluBranchMOp<DestReg, SrcRegWidth>),
#[extract(alu_branch_mop, alu_branch_mop_sim, alu_branch_mop_sim_ref, alu_branch_mop_sim_mut)]
AluBranch(AluBranchMOp<DestReg, SrcReg>),
#[transformed_move]
#[create_dyn_unit_fn = |config, unit_index| todo!()]
#[extract = transformed_move_mop]
#[extract(transformed_move_mop, transformed_move_mop_sim, transformed_move_mop_sim_ref, transformed_move_mop_sim_mut)]
TransformedMove(TransformedMoveOp),
#[create_dyn_unit_fn = |config, unit_index| todo!()]
#[extract = load_store_mop]
LoadStore(LoadStoreMOp<DestReg, SrcRegWidth>),
#[extract(load_store_mop, load_store_mop_sim, load_store_mop_sim_ref, load_store_mop_sim_mut)]
LoadStore(LoadStoreMOp<DestReg, SrcReg>),
}
}
#[hdl]
pub struct GlobalState {
pub flags_mode: FlagsMode,
}
#[hdl(cmp_eq)]
pub struct UnitResultCompleted<ExtraOut> {
pub value: PRegValue,
pub extra_out: ExtraOut,
}
#[hdl(cmp_eq)]
pub struct UnitOutputWrite<OutRegNumWidth: Size> {
pub which: UnitOutRegNum<OutRegNumWidth>,
pub value: PRegValue,
}
#[hdl(cmp_eq)]
pub struct TrapData {
// TODO
}
#[hdl]
pub enum UnitResult<ExtraOut> {
Completed(UnitResultCompleted<ExtraOut>),
Trap(TrapData),
}
impl<ExtraOut: Type> UnitResult<ExtraOut> {
pub fn extra_out_ty(self) -> ExtraOut {
self.Completed.extra_out
}
}
#[hdl]
pub struct UnitOutput<OutRegNumWidth: Size, ExtraOut> {
pub which: UnitOutRegNum<OutRegNumWidth>,
pub result: UnitResult<ExtraOut>,
}
impl<OutRegNumWidth: Size, ExtraOut: Type> UnitOutput<OutRegNumWidth, ExtraOut> {
pub fn extra_out_ty(self) -> ExtraOut {
self.result.extra_out_ty()
}
}
#[hdl(cmp_eq)]
pub struct UnitCancelInput<OutRegNumWidth: Size> {
pub which: UnitOutRegNum<OutRegNumWidth>,
}
pub trait UnitTrait:
'static + Send + Sync + std::fmt::Debug + fayalite::intern::SupportsPtrEqWithTypeId
{
type Type: BundleType;
type ExtraOut: Type;
type MOp: Type;
fn ty(&self) -> Self::Type;
fn extra_out_ty(&self) -> Self::ExtraOut;
fn mop_ty(&self) -> Self::MOp;
fn unit_kind(&self) -> UnitKind;
fn extract_mop(
&self,
mop: Expr<RenamedMOp<UnitOutRegNum<DynSize>, DynSize>>,
) -> Expr<HdlOption<Self::MOp>>;
fn module(&self) -> Interned<Module<Self::Type>>;
fn unit_to_reg_alloc(
fn cd(&self, this: Expr<Self::Type>) -> Option<Expr<ClockDomain>>;
fn from_execute(
&self,
this: Expr<Self::Type>,
) -> Expr<UnitToRegAlloc<Self::MOp, Self::ExtraOut, DynSize, DynSize, DynSize>>;
fn cd(&self, this: Expr<Self::Type>) -> Expr<ClockDomain>;
fn global_state(&self, this: Expr<Self::Type>) -> Expr<GlobalState>;
) -> Expr<ExecuteToUnitInterface<PhantomConst<CpuConfig>>>;
fn to_dyn(&self) -> DynUnit;
}
type DynUnitTrait = dyn UnitTrait<Type = Bundle, ExtraOut = CanonicalType, MOp = CanonicalType>;
type DynUnitTrait = dyn UnitTrait<Type = Bundle>;
impl fayalite::intern::InternedCompare for DynUnitTrait {
type InternedCompareKey = fayalite::intern::PtrEqWithTypeId;
@ -343,57 +367,34 @@ impl fayalite::intern::InternedCompare for DynUnitTrait {
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct DynUnit {
ty: Bundle,
extra_out_ty: CanonicalType,
mop_ty: CanonicalType,
unit_kind: UnitKind,
unit: Interned<DynUnitTrait>,
}
impl UnitTrait for DynUnit {
type Type = Bundle;
type ExtraOut = CanonicalType;
type MOp = CanonicalType;
fn ty(&self) -> Self::Type {
self.ty
}
fn extra_out_ty(&self) -> Self::ExtraOut {
self.extra_out_ty
}
fn mop_ty(&self) -> Self::MOp {
self.mop_ty
}
fn unit_kind(&self) -> UnitKind {
self.unit_kind
}
fn extract_mop(
&self,
mop: Expr<RenamedMOp<UnitOutRegNum<DynSize>, DynSize>>,
) -> Expr<HdlOption<Self::MOp>> {
self.unit.extract_mop(mop)
}
fn module(&self) -> Interned<Module<Self::Type>> {
self.unit.module()
}
fn unit_to_reg_alloc(
&self,
this: Expr<Self::Type>,
) -> Expr<UnitToRegAlloc<Self::MOp, Self::ExtraOut, DynSize, DynSize, DynSize>> {
self.unit.unit_to_reg_alloc(this)
}
fn cd(&self, this: Expr<Self::Type>) -> Expr<ClockDomain> {
fn cd(&self, this: Expr<Self::Type>) -> Option<Expr<ClockDomain>> {
self.unit.cd(this)
}
fn global_state(&self, this: Expr<Self::Type>) -> Expr<GlobalState> {
self.unit.global_state(this)
fn from_execute(
&self,
this: Expr<Self::Type>,
) -> Expr<ExecuteToUnitInterface<PhantomConst<CpuConfig>>> {
self.unit.from_execute(this)
}
fn to_dyn(&self) -> DynUnit {
@ -406,59 +407,34 @@ pub struct DynUnitWrapper<T>(pub T);
impl<T: UnitTrait + Clone + std::hash::Hash + Eq> UnitTrait for DynUnitWrapper<T> {
type Type = Bundle;
type ExtraOut = CanonicalType;
type MOp = CanonicalType;
fn ty(&self) -> Self::Type {
Bundle::from_canonical(self.0.ty().canonical())
}
fn extra_out_ty(&self) -> Self::ExtraOut {
self.0.extra_out_ty().canonical()
}
fn mop_ty(&self) -> Self::MOp {
self.0.mop_ty().canonical()
}
fn unit_kind(&self) -> UnitKind {
self.0.unit_kind()
}
fn extract_mop(
&self,
mop: Expr<RenamedMOp<UnitOutRegNum<DynSize>, DynSize>>,
) -> Expr<HdlOption<Self::MOp>> {
Expr::from_enum(Expr::as_enum(self.0.extract_mop(mop)))
}
fn module(&self) -> Interned<Module<Self::Type>> {
self.0.module().canonical().intern_sized()
}
fn unit_to_reg_alloc(
&self,
this: Expr<Self::Type>,
) -> 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)),
))
}
fn cd(&self, this: Expr<Self::Type>) -> Expr<ClockDomain> {
fn cd(&self, this: Expr<Self::Type>) -> Option<Expr<ClockDomain>> {
self.0.cd(Expr::from_bundle(this))
}
fn global_state(&self, this: Expr<Self::Type>) -> Expr<GlobalState> {
self.0.global_state(Expr::from_bundle(this))
fn from_execute(
&self,
this: Expr<Self::Type>,
) -> Expr<ExecuteToUnitInterface<PhantomConst<CpuConfig>>> {
self.0.from_execute(Expr::from_bundle(this))
}
fn to_dyn(&self) -> DynUnit {
let unit = self.intern();
DynUnit {
ty: unit.ty(),
extra_out_ty: unit.extra_out_ty(),
mop_ty: unit.mop_ty(),
unit_kind: unit.unit_kind(),
unit: Interned::cast_unchecked(unit, |v: &Self| -> &DynUnitTrait { v }),
}

File diff suppressed because it is too large Load diff

View file

@ -1,615 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
config::CpuConfig,
instruction::{MOpTrait, PRegNum, UnitNum, UnitOutRegNum, COMMON_MOP_SRC_LEN},
register::PRegValue,
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::ready_valid::ReadyValid,
};
use std::marker::PhantomData;
#[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]
pub struct ExecuteEnd<OutRegNumWidth: Size, ExtraOut> {
pub unit_output: UnitOutput<OutRegNumWidth, ExtraOut>,
}
#[hdl]
enum InFlightOpState {
Ready,
Running,
CanceledAndRunning,
}
impl InFlightOpState {
fn ready_next_state(canceling: bool, starting: bool, ending: bool) -> Expr<HdlOption<Self>> {
match (canceling, starting, ending) {
(false, false, _) => HdlSome(InFlightOpState.Ready()),
(false, true, false) => HdlSome(InFlightOpState.Running()),
(false, true, true) => HdlNone(),
(true, false, _) => HdlNone(),
(true, true, false) => HdlSome(InFlightOpState.CanceledAndRunning()),
(true, true, true) => HdlNone(),
}
}
fn running_next_state(canceling: bool, _starting: bool, ending: bool) -> Expr<HdlOption<Self>> {
match (canceling, ending) {
(false, false) => HdlSome(InFlightOpState.Running()),
(false, true) => HdlNone(),
(true, false) => HdlSome(InFlightOpState.CanceledAndRunning()),
(true, true) => HdlNone(),
}
}
fn canceled_and_running_next_state(
_canceling: bool,
_starting: bool,
ending: bool,
) -> Expr<HdlOption<Self>> {
if ending {
HdlNone()
} else {
HdlSome(InFlightOpState.CanceledAndRunning())
}
}
/// FIXME: this is working around #[hdl] match not supporting matching values inside structs yet
#[hdl]
fn connect_next_state(
canceling: Expr<Bool>,
starting: Expr<Bool>,
ending: Expr<Bool>,
next_state_fn: fn(canceling: bool, starting: bool, ending: bool) -> Expr<HdlOption<Self>>,
next_state: Expr<HdlOption<Self>>,
) {
#[hdl]
fn recurse<const N: usize>(
exprs: &[Expr<Bool>; N],
bools: &mut [bool; N],
f: &mut impl FnMut(&[bool; N]),
arg_index: usize,
) {
if arg_index < N {
#[hdl]
if exprs[arg_index] {
bools[arg_index] = true;
recurse(exprs, bools, f, arg_index + 1);
} else {
bools[arg_index] = false;
recurse(exprs, bools, f, arg_index + 1);
}
} else {
f(bools);
}
}
recurse(
&[canceling, starting, ending],
&mut [false; 3],
&mut |&[canceling, starting, ending]| {
connect(next_state, next_state_fn(canceling, starting, ending))
},
0,
);
}
}
#[hdl]
struct InFlightOp<MOp: Type> {
state: InFlightOpState,
mop: MOp,
pc: UInt<64>,
src_ready_flags: Array<Bool, { COMMON_MOP_SRC_LEN }>,
}
#[hdl]
struct InFlightOpsSummary<OpIndexWidth: Size> {
empty_op_index: HdlOption<UIntType<OpIndexWidth>>,
ready_op_index: HdlOption<UIntType<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<MOp>>>,
) -> Expr<Self> {
let empty_op_index = wire_with_loc(
&format!("empty_op_index_{op_index}"),
SourceLocation::caller(),
HdlOption[op_index_ty],
);
connect(empty_op_index, HdlOption[op_index_ty].HdlNone());
let ready_op_index = wire_with_loc(
&format!("ready_op_index_{op_index}"),
SourceLocation::caller(),
HdlOption[op_index_ty],
);
connect(ready_op_index, HdlOption[op_index_ty].HdlNone());
#[hdl]
if let HdlSome(in_flight_op) = in_flight_op {
#[hdl]
let InFlightOp::<_> {
state,
mop: _,
pc: _,
src_ready_flags,
} = in_flight_op;
connect(ready_op_index, HdlOption[op_index_ty].HdlNone());
#[hdl]
match state {
InFlightOpState::Ready =>
{
#[hdl]
if src_ready_flags.cmp_eq([true; COMMON_MOP_SRC_LEN]) {
connect(ready_op_index, HdlSome(op_index.cast_to(op_index_ty)));
}
}
InFlightOpState::CanceledAndRunning | InFlightOpState::Running => {}
}
} else {
connect(empty_op_index, HdlSome(op_index.cast_to(op_index_ty)));
}
#[hdl]
InFlightOpsSummary::<_> {
empty_op_index,
ready_op_index,
}
}
#[hdl]
fn combine(l: impl ToExpr<Type = Self>, r: impl ToExpr<Type = Self>) -> Expr<Self> {
let l = l.to_expr();
let r = r.to_expr();
#[hdl]
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),
}
}
}
impl InFlightOpsSummary<DynSize> {
fn summarize<MOp: Type, MaxInFlight: Size>(
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 index_range = 0..max_in_flight;
let index_ty = UInt::range(index_range.clone());
tree_reduce(
index_range.map(|i| Self::new(i, index_ty, in_flight_ops[i])),
Self::combine,
)
.expect("in_flight_ops is known to have len > 0")
}
}
#[hdl_module]
pub fn unit_base<
MOp: Type + MOpTrait<DestReg = UnitOutRegNum<DynSize>, SrcRegWidth = DynSize>,
ExtraOut: Type,
>(
config: &CpuConfig,
unit_index: usize,
mop_ty: MOp,
extra_out_ty: ExtraOut,
) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
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<MOp>> = m.output(ReadyValid[ExecuteStart[mop_ty]]);
#[hdl]
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());
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)
.reset(repeat(HdlOption[in_flight_op_ty].HdlNone(), max_in_flight));
let in_flight_ops_summary_value = InFlightOpsSummary::summarize(in_flight_ops);
#[hdl]
let in_flight_ops_summary = wire(Expr::ty(in_flight_ops_summary_value));
connect(in_flight_ops_summary, in_flight_ops_summary_value);
connect(
unit_to_reg_alloc.input.ready,
HdlOption::is_some(in_flight_ops_summary.empty_op_index),
);
#[hdl]
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(config.p_reg_num().const_zero().cast_to_bits(), ConstUsize),
);
#[hdl]
let read_src_values = wire();
connect(read_src_values, [PRegValue::zeroed(); COMMON_MOP_SRC_LEN]);
#[hdl]
let input_src_regs = wire(mop_ty.src_regs_ty());
connect(
input_src_regs,
repeat(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())
.map(|unit_index| {
let mut mem = memory_with_loc(
&format!("unit_{unit_index}_output_regs_valid"),
Bool,
SourceLocation::caller(),
);
mem.depth(1 << config.out_reg_num_width);
mem
})
.collect();
for unit_index in 0..Expr::ty(unit_output_writes).len() {
let mut unit_output_regs = memory_with_loc(
&format!("unit_{unit_index}_output_regs"),
PRegValue,
SourceLocation::caller(),
);
unit_output_regs.depth(1 << 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(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);
#[hdl]
if UnitNum::is_index(p_reg_num.unit_num, unit_index) {
connect(read_port.en, true);
connect(read_src_values[src_index], read_port.data);
}
}
for src_index in 0..COMMON_MOP_SRC_LEN {
let read_port = unit_output_regs_valid[unit_index].new_read_port();
let p_reg_num = input_src_regs[src_index].cast_bits_to(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);
#[hdl]
if UnitNum::is_index(p_reg_num.unit_num, unit_index) {
connect(read_port.en, true);
connect(input_src_regs_valid[src_index], read_port.data);
}
}
let write_port = unit_output_regs.new_write_port();
connect_any(write_port.addr, 0u8);
connect(write_port.en, false);
connect(write_port.clk, cd.clk);
connect(write_port.data, PRegValue::zeroed());
connect(write_port.mask, splat_mask(PRegValue, true.to_expr()));
let ready_write_port = unit_output_regs_valid[unit_index].new_write_port();
connect_any(ready_write_port.addr, 0u8);
connect(ready_write_port.en, false);
connect(ready_write_port.clk, cd.clk);
connect(ready_write_port.data, true);
connect(ready_write_port.mask, true);
#[hdl]
if let HdlSome(unit_output_write) = unit_output_writes[unit_index] {
connect_any(write_port.addr, unit_output_write.which.value);
connect(write_port.data, unit_output_write.value);
connect(write_port.en, true);
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: config.unit_num().from_index(unit_index),
unit_out_reg: unit_output_write.which,
};
for src_index in 0..COMMON_MOP_SRC_LEN {
#[hdl]
if input_src_regs[src_index].cmp_eq(p_reg_num.cast_to_bits()) {
connect(input_src_regs_valid[src_index], true);
}
}
}
let free_write_port = unit_output_regs_valid[unit_index].new_write_port();
connect_any(free_write_port.addr, 0u8);
connect(free_write_port.en, false);
connect(free_write_port.clk, cd.clk);
connect(free_write_port.data, false);
connect(free_write_port.mask, true);
#[hdl]
if let HdlSome(unit_reg_free) = unit_reg_frees[unit_index] {
connect_any(free_write_port.addr, unit_reg_free.value);
connect(free_write_port.en, true);
}
}
#[hdl]
if let HdlSome(ready_op_index) = in_flight_ops_summary.ready_op_index {
#[hdl]
if let HdlSome(in_flight_op) = in_flight_ops[ready_op_index] {
connect(
execute_start.data,
HdlSome(
#[hdl]
ExecuteStart::<_> {
mop: in_flight_op.mop,
pc: in_flight_op.pc,
src_values: read_src_values,
},
),
);
}
}
connect(
unit_to_reg_alloc.output,
Expr::ty(unit_to_reg_alloc.output).HdlNone(),
);
#[hdl]
let input_in_flight_op = wire(HdlOption[in_flight_op_ty]);
connect(input_in_flight_op, HdlOption[in_flight_op_ty].HdlNone());
#[hdl]
if let HdlSome(input) = ReadyValid::firing_data(unit_to_reg_alloc.input) {
#[hdl]
let UnitInput::<_> { mop, pc } = input;
#[hdl]
let input_mop_src_regs = wire(mop_ty.src_regs_ty());
connect(
input_mop_src_regs,
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(
"input_in_flight_op_src_ready_flags",
SourceLocation::caller(),
StaticType::TYPE,
);
connect(src_ready_flags, input_src_regs_valid);
connect(input_src_regs, input_mop_src_regs);
#[hdl]
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::<_> {
state: InFlightOpState.Ready(),
mop,
pc,
src_ready_flags,
},
),
);
}
#[hdl]
if let HdlSome(empty_op_index) = in_flight_ops_summary.empty_op_index {
connect(in_flight_ops[empty_op_index], input_in_flight_op);
}
}
#[hdl]
let in_flight_op_next_state = wire(Array[HdlOption[InFlightOpState]][max_in_flight]);
#[hdl]
let in_flight_op_next_src_ready_flags =
wire(Array[in_flight_op_ty.src_ready_flags][max_in_flight]);
#[hdl]
let in_flight_op_canceling = wire(Array[Bool][max_in_flight]);
#[hdl]
let in_flight_op_execute_starting = wire(Array[Bool][max_in_flight]);
#[hdl]
let in_flight_op_execute_ending = wire(Array[Bool][max_in_flight]);
for in_flight_op_index in 0..max_in_flight {
connect(
in_flight_op_next_src_ready_flags[in_flight_op_index],
[false; COMMON_MOP_SRC_LEN],
);
connect(in_flight_op_canceling[in_flight_op_index], false);
connect(in_flight_op_execute_starting[in_flight_op_index], false);
connect(in_flight_op_execute_ending[in_flight_op_index], false);
#[hdl]
if let HdlSome(in_flight_op) = in_flight_ops[in_flight_op_index] {
#[hdl]
let InFlightOp::<_> {
state,
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(),
mop_ty.src_regs_ty(),
);
connect(
src_regs,
repeat(config.p_reg_num().const_zero().cast_to_bits(), ConstUsize),
);
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),
)) {
connect(read_src_regs, src_regs);
}
connect(
in_flight_op_next_src_ready_flags[in_flight_op_index],
src_ready_flags,
);
for unit_index in 0..Expr::ty(unit_output_writes).len() {
#[hdl]
if let HdlSome(unit_output_write) = unit_output_writes[unit_index] {
#[hdl]
let UnitOutputWrite::<_> {
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,
};
for src_index in 0..COMMON_MOP_SRC_LEN {
#[hdl]
if p_reg_num.cast_to_bits().cmp_eq(src_regs[src_index]) {
connect(
in_flight_op_next_src_ready_flags[in_flight_op_index][src_index],
true,
);
}
}
}
}
connect(
in_flight_op_canceling[in_flight_op_index],
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 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] {
#[hdl]
match state {
InFlightOpState::Running | InFlightOpState::Ready => {
connect(unit_to_reg_alloc.output, HdlSome(unit_output))
}
InFlightOpState::CanceledAndRunning => {}
}
}
}
}
#[hdl]
if let HdlSome(execute_start) = ReadyValid::firing_data(execute_start) {
#[hdl]
if which.cmp_eq(MOp::dest_reg(execute_start.mop)) {
connect(in_flight_op_execute_starting[in_flight_op_index], true);
}
}
let connect_next_state = |f| {
InFlightOpState::connect_next_state(
in_flight_op_canceling[in_flight_op_index],
in_flight_op_execute_starting[in_flight_op_index],
in_flight_op_execute_ending[in_flight_op_index],
f,
in_flight_op_next_state[in_flight_op_index],
);
};
#[hdl]
match state {
InFlightOpState::Ready => connect_next_state(InFlightOpState::ready_next_state),
InFlightOpState::Running => connect_next_state(InFlightOpState::running_next_state),
InFlightOpState::CanceledAndRunning => {
connect_next_state(InFlightOpState::canceled_and_running_next_state);
}
}
#[hdl]
if let HdlSome(state) = in_flight_op_next_state[in_flight_op_index] {
connect(
in_flight_ops[in_flight_op_index],
HdlSome(
#[hdl]
InFlightOp::<_> {
state,
mop,
pc,
src_ready_flags: in_flight_op_next_src_ready_flags[in_flight_op_index],
},
),
);
} else {
connect(
in_flight_ops[in_flight_op_index],
HdlOption[in_flight_op_ty].HdlNone(),
);
}
} else {
connect(in_flight_op_next_state[in_flight_op_index], HdlNone());
}
}
}

View file

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

View file

@ -1,147 +1,28 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use fayalite::{
expr::ops::{ExprCastTo, ExprIndex, ExprPartialEq, ExprPartialOrd},
int::SizeType,
intern::{Intern, Interned},
prelude::*,
ty::{MatchVariantWithoutScope, StaticType, TypeProperties},
};
use std::{marker::PhantomData, ops::Index};
use fayalite::{expr::ops::ExprIndex, int::UIntInRangeInclusiveType, prelude::*};
use std::fmt;
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct Length<Max: Size> {
ty: UInt,
_phantom: PhantomData<Max>,
#[derive(Clone, Debug)]
pub struct ArrayVecFullError<V, I: Iterator> {
pub value: V,
pub rest: std::iter::Chain<std::iter::Once<I::Item>, I>,
}
impl<Max: Size> Length<Max> {
pub fn new(max: Max::SizeType) -> Self {
Self {
ty: UInt::range_inclusive(0..=Max::as_usize(max)),
_phantom: PhantomData,
}
}
pub fn ty(self) -> UInt {
self.ty
}
pub fn zero(self) -> Expr<Self> {
Self::from_uint_unchecked(self.ty.zero())
}
pub fn from_uint_unchecked(v: impl ToExpr<Type = UInt>) -> Expr<Self> {
Expr::from_canonical(Expr::canonical(v.to_expr()))
}
pub fn cast_from_uint_unchecked<SrcWidth: Size>(
self,
v: impl ToExpr<Type = UIntType<SrcWidth>>,
) -> Expr<Self> {
Self::from_uint_unchecked(v.to_expr().cast_to(self.ty))
}
pub fn as_uint(this: impl ToExpr<Type = Self>) -> Expr<UInt> {
let this = this.to_expr();
this.cast_to(Expr::ty(this).ty)
impl<V, I: Iterator> fmt::Display for ArrayVecFullError<V, I> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ArrayVec is full")
}
}
impl<Max: Size, 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)
}
impl<V: fmt::Debug, I: Iterator<Item: fmt::Debug> + fmt::Debug> std::error::Error
for ArrayVecFullError<V, I>
{
}
#[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]
@ -156,7 +37,55 @@ 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 from_iter_sim<I: IntoIterator<Item: ToSimValueWithType<T>>>(
self,
uninit_element: impl ToSimValueWithType<T>,
iter: I,
) -> Result<SimValue<Self>, ArrayVecFullError<SimValue<Self>, I::IntoIter>> {
let mut value = Self::new_sim(self, uninit_element);
let element = self.element();
let mut iter = iter.into_iter();
for i in 0..self.capacity() {
let Some(v) = iter.next() else {
break;
};
value.elements[i] = v.into_sim_value_with_type(element);
*value.len = i + 1;
}
if let Some(extra) = iter.next() {
Err(ArrayVecFullError {
value,
rest: std::iter::once(extra).chain(iter),
})
} else {
Ok(value)
}
}
pub fn element(self) -> T {
@ -176,8 +105,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]
@ -189,9 +118,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()
@ -207,11 +139,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())],
@ -226,7 +224,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));
@ -234,15 +232,42 @@ 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))
@ -263,3 +288,34 @@ where
<ArrayType<T, N> as ExprIndex<Idx>>::expr_index(&this.elements, index)
}
}
impl<T: Type> ArrayVec<T, ConstUsize<1>> {
#[hdl]
pub fn from_opt_sim(
opt: impl ToSimValue<Type = HdlOption<T>>,
uninit_element: impl ToSimValueWithType<T>,
) -> SimValue<Self> {
let opt = opt.into_sim_value();
let ty = ArrayVec[opt.ty().HdlSome][ConstUsize];
#[hdl(sim)]
match opt {
HdlSome(v) => ty.new_full_sim([v]),
HdlNone => ty.new_sim(uninit_element),
}
}
#[hdl]
pub fn into_opt_sim(this: impl ToSimValue<Type = Self>) -> SimValue<HdlOption<T>> {
let this = this.into_sim_value();
#[hdl(sim)]
let Self { elements, len } = this;
let [element] = SimValue::into_value(elements);
let ty = HdlOption[element.ty()];
if *len == 0 {
#[hdl(sim)]
ty.HdlNone()
} else {
#[hdl(sim)]
ty.HdlSome(element)
}
}
}

13395
crates/cpu/tests/expected/fetch.vcd generated Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

154224
crates/cpu/tests/expected/next_pc.vcd generated Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

114674
crates/cpu/tests/expected/simple_uart.vcd generated Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

707
crates/cpu/tests/fetch.rs Normal file
View file

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

View file

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

View file

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

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

File diff suppressed because it is too large Load diff

View file

@ -1,18 +1,17 @@
// 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},
instruction::{AddSubMOp, LogicalMOp, Lut4, MOp, MOpDestReg, MOpRegNum, OutputIntegerMode},
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;
@ -61,7 +60,7 @@ fn test_reg_alloc() {
[HdlSome(()), HdlNone()],
},
[0u8; 2],
0x12345678u32.cast_to_static(),
0x12345678u32.cast_to_static::<SInt<_>>(),
OutputIntegerMode.DupLow32(),
false,
false,
@ -83,7 +82,7 @@ fn test_reg_alloc() {
[HdlSome(()), HdlNone()],
},
[1u8, 0, 0],
1.cast_to_static(),
1.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
false,
@ -101,9 +100,9 @@ fn test_reg_alloc() {
flag_regs: [HdlNone(), HdlSome(())],
},
[2u8, 4u8],
0.cast_to_static(),
0.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
0b0110_hdl_u4,
Lut4::from_fn(|a, b| a ^ b),
),
];
let insns = insns_init.into_iter().chain(insns_loop.into_iter().cycle());

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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()
{
@ -45,12 +46,15 @@ function main()
*/LICENSE.md|*/Notices.txt)
# copyright file
;;
/crates/cpu/tests/expected/*.vcd|/crates/cpu/tests/expected/*.txt)
/crates/cpu/tests*/expected/*.vcd|/crates/cpu/tests*/expected/*.txt)
# file that can't contain copyright header
;;
/.forgejo/workflows/*.yml|*/.gitignore|*.toml)
/.forgejo/workflows/*.yml|*/.gitignore|*/.gitattributes|*.toml)
check_file "$file" "${POUND_HEADER[@]}"
;;
*.mermaid)
check_file "$file" "${MERMAID_HEADER[@]}"
;;
*.md)
check_file "$file" "${MD_HEADER[@]}"
;;
@ -67,4 +71,4 @@ function main()
done
}
main
main