mirror of
https://github.com/zama-ai/tfhe-rs.git
synced 2026-01-08 22:28:01 -05:00
feat(hpu): Add support for tandem pdi
Add support for hpu archive and Fpga reloading. Rely on Tandem implementation for hot-reloading of FPGA. Add reload procedure inside ffi/v80 backend. Now when starting application on HpuV80, a first check of version is done. If version mismatch, a pdi reload is triggered
This commit is contained in:
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.hpu filter=lfs diff=lfs merge=lfs -text
|
||||
@@ -12,10 +12,10 @@ keywords = ["encryption", "fhe", "cryptography", "hardware", "fpga"]
|
||||
|
||||
[features]
|
||||
hw-xrt = []
|
||||
hw-v80 = []
|
||||
hw-v80 = ["bincode"]
|
||||
io-dump = ["num-traits"]
|
||||
rtl_graph = ["dot2"]
|
||||
utils = ["clap", "clap-num", "bitvec", "serde_json"]
|
||||
utils = ["clap", "clap-num", "bitvec", "serde_json", "bincode", "serde_derive"]
|
||||
|
||||
[build-dependencies]
|
||||
cxx-build = "1.0"
|
||||
@@ -52,7 +52,7 @@ ipc-channel = "0.18.3"
|
||||
num-traits = { version = "0.2", optional = true }
|
||||
clap = { version = "4.4.4", features = ["derive"], optional = true }
|
||||
clap-num = { version = "1.1.1", optional = true }
|
||||
nix = { version = "0.29.0", features = ["ioctl", "uio"] }
|
||||
nix = { version = "0.29.0", features = ["ioctl", "uio", "fs"] }
|
||||
|
||||
# Dependencies used for rtl_graph features
|
||||
dot2 = { version = "1.0", optional = true }
|
||||
@@ -60,6 +60,10 @@ dot2 = { version = "1.0", optional = true }
|
||||
bitvec = { version = "1.0", optional = true }
|
||||
serde_json = { version = "1.0", optional = true }
|
||||
|
||||
# Dependencies used for v80 pdi handling
|
||||
bincode ={ version = "1.3", optional = true}
|
||||
serde_derive ={ version = "1.0", optional = true}
|
||||
|
||||
# Binary for manual debugging
|
||||
# Enable to access Hpu register and drive some custom sequence by hand
|
||||
[[bin]]
|
||||
@@ -67,6 +71,11 @@ name = "hputil"
|
||||
path = "src/utils/hputil.rs"
|
||||
required-features = ["utils"]
|
||||
|
||||
[[bin]]
|
||||
name = "pdi_mgmt"
|
||||
path = "src/utils/pdi_mgmt.rs"
|
||||
required-features = ["utils", "hw-v80"]
|
||||
|
||||
# Binary for asm manipulation
|
||||
# Enable to convert back and forth between asm/hex format
|
||||
[[bin]]
|
||||
|
||||
@@ -200,7 +200,7 @@ There are some example applications already available in `tfhe/examples/hpu`:
|
||||
|
||||
In order to run those applications on hardware, user must build from the project root (i.e `tfhe-rs-internal`) with `hpu-v80` features:
|
||||
|
||||
> NB: Running examples required to have correctly pulled the `.pdi` files. Those files, due to their size, are backed by git-lfs and disabled by default.
|
||||
> NB: Running examples required to have correctly pulled the `.hpu` files. Those files, due to their size, are backed by git-lfs and disabled by default.
|
||||
> In order to retrieve them, use the following command:
|
||||
> ```bash
|
||||
> git lfs pull --include="*" --exclude=""
|
||||
@@ -209,11 +209,18 @@ In order to run those applications on hardware, user must build from the project
|
||||
``` bash
|
||||
cargo build --release --features="hpu-v80" --example hpu_hlapi --example hpu_bench
|
||||
# Correctly setup environment with setup_hpu.sh script
|
||||
source setup_hpu.sh --config v80 --init-qdma
|
||||
source setup_hpu.sh --config v80
|
||||
./target/release/examples/hpu_bench --integer-w 64 --integer-w 32 --iop MUL --iter 10
|
||||
./target/release/examples/hpu_hlapi
|
||||
```
|
||||
|
||||
> NB: Error that occurred when ".hpu" files weren't correctly fetch could be a bit enigmatic: `memory allocation of ... bytes failed`
|
||||
> If you encountered this issue, you should run the following command:
|
||||
> ```bash
|
||||
> git lfs pull --include="*" --exclude=""
|
||||
> ```
|
||||
|
||||
|
||||
## Test framework
|
||||
There is also a set of tests backed in tfhe-rs. Tests are gather in testbundle over various integer width.
|
||||
Those tests have 5 sub-kind:
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
"${HPU_BACKEND_DIR}/config_store/${HPU_CONFIG}/hpu_regif_core_prc_3in3.toml"]
|
||||
polling_us=10
|
||||
[fpga.ffi.V80]
|
||||
ami_id=1 # First ami device in the list
|
||||
id= 0
|
||||
hpu_path="${HPU_BACKEND_DIR}/config_store/v80_archives/psi64.hpu"
|
||||
ami_path="${AMI_PATH}/ami.ko"
|
||||
qdma_h2c="/dev/qdma${V80_PCIE_DEV}001-MM-1"
|
||||
qdma_c2h="/dev/qdma${V80_PCIE_DEV}001-MM-2"
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# Hpu archives
|
||||
|
||||
This folder contains a set of custom archive used to reload the FPGA.
|
||||
This archives contains pdi files and other metadata.
|
||||
A tool `pdi_mgmt` is provided to create/expand archives.
|
||||
|
||||
|
||||
|
||||
## How to build `pdi_mgmt`
|
||||
Simply go in tfhe-hpu-backend folder and use cargo:
|
||||
```
|
||||
cargo build --release --features hw-v80,utils
|
||||
```
|
||||
|
||||
|
||||
## How to unpack an archive for inspection
|
||||
For this purpose use `pdi_mgmt`:
|
||||
```
|
||||
./target/devo/pdi_mgmt unpack backends/tfhe-hpu-backend/config_store/v80_pdi/psi64.hpu backends/tfhe-hpu-backend/config_store/v80_pdi/psi64
|
||||
```
|
||||
|
||||
|
||||
## How to pack an archive after update
|
||||
For example, if you have previously unpack the psi64.hpu, you can use the following command to pack it back:
|
||||
|
||||
```
|
||||
./target/devo/pdi_mgmt pack backends/tfhe-hpu-backend/config_store/v80_pdi/psi64 backends/tfhe-hpu-backend/config_store/v80_pdi/psi64.hpu
|
||||
```
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d5f578ec0cbcd1525fc88dc57fff1a2384fa742a147f69b6a9c77deafc0601fe
|
||||
size 33348376
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:cb9ebedd0987130c4f6e1ef09f279d92f083815c1383da4b257198a33ab4881e
|
||||
size 80293531
|
||||
16
backends/tfhe-hpu-backend/scripts/pdi_jtag.tcl
Normal file
16
backends/tfhe-hpu-backend/scripts/pdi_jtag.tcl
Normal file
@@ -0,0 +1,16 @@
|
||||
set TOP_NAME [lindex $::argv 0]
|
||||
|
||||
open_hw_manager
|
||||
|
||||
connect_hw_server -allow_non_jtag
|
||||
open_hw_target
|
||||
current_hw_device [get_hw_devices xcv80_1]
|
||||
refresh_hw_device -update_hw_probes false [lindex [get_hw_devices xcv80_1] 0]
|
||||
|
||||
set_property PROBES.FILE {} [get_hw_devices xcv80_1]
|
||||
set_property FULL_PROBES.FILE {} [get_hw_devices xcv80_1]
|
||||
|
||||
# stage 1 programming
|
||||
set_property PROGRAM.FILE $TOP_NAME [get_hw_devices xcv80_1]
|
||||
program_hw_devices [get_hw_devices xcv80_1]
|
||||
refresh_hw_device [lindex [get_hw_devices xcv80_1] 0]
|
||||
17
backends/tfhe-hpu-backend/scripts/v80-pcie-perms.sh
Executable file
17
backends/tfhe-hpu-backend/scripts/v80-pcie-perms.sh
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
GROUP=hw;
|
||||
# Enable Pcie rescan
|
||||
if [ -e /sys/bus/pci/rescan ]; then
|
||||
chgrp $GROUP /sys/bus/pci/rescan && chmod g+w /sys/bus/pci/rescan
|
||||
fi
|
||||
# Search V80 pcie boards PF0
|
||||
for dev in $(lspci -nn -d 10ee:50b4 | awk '{print $1}'); do
|
||||
chgrp -R $GROUP /sys/bus/pci/devices/0000\:$dev/
|
||||
chmod -R g=u /sys/bus/pci/devices/0000\:$dev/
|
||||
done
|
||||
# Search V80 pcie boards PF1
|
||||
for dev in $(lspci -nn -d 10ee:50b5 | awk '{print $1}'); do
|
||||
chgrp -R $GROUP /sys/bus/pci/devices/0000\:$dev/
|
||||
chmod -R g=u /sys/bus/pci/devices/0000\:$dev/
|
||||
done
|
||||
@@ -168,11 +168,15 @@ impl HpuHw {
|
||||
{
|
||||
match mode {
|
||||
FFIMode::V80 {
|
||||
ami_id,
|
||||
id,
|
||||
hpu_path,
|
||||
ami_path,
|
||||
qdma_h2c,
|
||||
qdma_c2h,
|
||||
} => Self(v80::HpuHw::new_hpu_hw(
|
||||
*ami_id,
|
||||
*id,
|
||||
&hpu_path.expand(),
|
||||
&ami_path.expand(),
|
||||
retry_rate,
|
||||
&qdma_h2c.expand(),
|
||||
&qdma_c2h.expand(),
|
||||
@@ -321,6 +325,9 @@ impl MemZone {
|
||||
|
||||
#[cfg(feature = "hw-v80")]
|
||||
mod v80;
|
||||
#[cfg(all(feature = "hw-v80", feature = "utils"))]
|
||||
pub use v80::HpuV80Pdi;
|
||||
|
||||
#[cfg(feature = "hw-xrt")]
|
||||
mod xrt;
|
||||
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
//! AMI driver is used to issue gcq command to the RPU
|
||||
//! Those command are used for configuration and register R/W
|
||||
use lazy_static::lazy_static;
|
||||
use std::error::Error;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::Read;
|
||||
use std::io::{BufRead, BufReader, Read};
|
||||
use std::os::fd::AsRawFd;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -15,7 +16,64 @@ const AMI_ID_FILE: &str = "/sys/bus/pci/drivers/ami/devices";
|
||||
const AMI_ID_PATTERN: &str = r"(?<pci>\d{2}:\d{2}\.\d)\s(?<dev_id>\d+)\s\d+";
|
||||
|
||||
const HIS_VERSION_FILE: &str = "/sys/bus/pci/devices/0000:${V80_PCIE_DEV}:00.0/amc_version";
|
||||
const HIS_VERSION_PATTERN: &str = r".*- zama ucore 2.0";
|
||||
const HIS_VERSION_PATTERN: &str = r".*- zama ucore (?<major>\d+).(?<minor>\d+)";
|
||||
|
||||
use crate::ffi::v80::pdi::metadata::Version;
|
||||
use crate::ffi::v80::pdi::uuid::AMI_UUID_WORDS;
|
||||
|
||||
const AMI_UUID_BAR_OFFSET: u64 = 0x1001000;
|
||||
|
||||
const AMI_DEVICES_MAP: &'static str = "/sys/bus/pci/drivers/ami/devices";
|
||||
|
||||
// NB: Some field available in the driver file were never used
|
||||
#[allow(dead_code)]
|
||||
pub struct AmiInfo {
|
||||
bus_id: usize,
|
||||
dev_id: usize,
|
||||
func_id: usize,
|
||||
devn: usize,
|
||||
hwmon: usize,
|
||||
}
|
||||
|
||||
/// Set of discovery function
|
||||
/// Enable to probe the device IDs and status
|
||||
impl AmiInfo {
|
||||
pub fn new() -> Result<Self, Box<dyn Error>> {
|
||||
// First read content of AMI_DEVICES_MAP
|
||||
let devices_file = OpenOptions::new()
|
||||
.read(true)
|
||||
.create(false)
|
||||
.open(AMI_DEVICES_MAP)?;
|
||||
|
||||
let devices_rd = BufReader::new(devices_file);
|
||||
// TODO handle multi-devices cases
|
||||
let line = devices_rd.lines().nth(1).ok_or("No device found")??;
|
||||
|
||||
// Second extract formatted information from string
|
||||
lazy_static! {
|
||||
static ref AMI_DEVICES_RE: regex::Regex = regex::Regex::new(
|
||||
r"(?<bus>[[:xdigit:]]{2}):(?<dev>[[:xdigit:]]{2})\.(?<func>[[:xdigit:]])\s(?<devn>\d+)\s(?<hwmon>\d+)"
|
||||
)
|
||||
.expect("Invalid regex");
|
||||
}
|
||||
|
||||
let caps = AMI_DEVICES_RE
|
||||
.captures(&line)
|
||||
.ok_or("Invalid AMI_DEVICES_MAP format")?;
|
||||
let bus_id = usize::from_str_radix(&caps["bus"], 16)?;
|
||||
let dev_id = usize::from_str_radix(&caps["dev"], 16)?;
|
||||
let func_id = usize::from_str_radix(&caps["func"], 16)?;
|
||||
let devn = caps["devn"].parse::<usize>()?;
|
||||
let hwmon = caps["hwmon"].parse::<usize>()?;
|
||||
Ok(Self {
|
||||
bus_id,
|
||||
dev_id,
|
||||
func_id,
|
||||
devn,
|
||||
hwmon,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AmiDriver {
|
||||
ami_dev: File,
|
||||
@@ -23,8 +81,14 @@ pub struct AmiDriver {
|
||||
}
|
||||
|
||||
impl AmiDriver {
|
||||
pub fn new(ami_id: usize, retry_rate: Duration) -> Self {
|
||||
Self::check_version();
|
||||
pub fn new(
|
||||
ami_id: u32,
|
||||
amc_ver: &Version,
|
||||
retry_rate: Duration,
|
||||
) -> Result<Self, Box<dyn Error>> {
|
||||
Self::check_version(amc_ver)?;
|
||||
// Read Ami info
|
||||
let ami_info = AmiInfo::new()?;
|
||||
|
||||
// Read ami_id_file to get ami device
|
||||
let ami_path = {
|
||||
@@ -38,7 +102,7 @@ impl AmiDriver {
|
||||
let ami_id_f = std::fs::read_to_string(AMI_ID_FILE).expect("Invalid ami_id filepath");
|
||||
let id_line = ami_id_f
|
||||
.lines()
|
||||
.nth(ami_id)
|
||||
.nth(ami_info.devn)
|
||||
.unwrap_or_else(|| panic!("Invalid ami id {ami_id}."));
|
||||
|
||||
let id_str = AMI_ID_RE
|
||||
@@ -51,24 +115,60 @@ impl AmiDriver {
|
||||
format!("/dev/ami{dev_id}")
|
||||
};
|
||||
|
||||
// Open ami device file
|
||||
let ami_dev = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(false)
|
||||
.open(&ami_path)
|
||||
.unwrap();
|
||||
Self {
|
||||
.open(ami_path)?;
|
||||
|
||||
Ok(Self {
|
||||
ami_dev,
|
||||
retry_rate,
|
||||
})
|
||||
}
|
||||
|
||||
/// Read currently loaded UUID in BAR
|
||||
pub fn uuid(&self) -> String {
|
||||
let ami_fd = self.ami_dev.as_raw_fd();
|
||||
|
||||
// Allocate heap memory for read value
|
||||
let uuid = Box::new([0_u32; AMI_UUID_WORDS]);
|
||||
let data_ptr = Box::into_raw(uuid);
|
||||
|
||||
// Populate payload
|
||||
let mut payload = AmiBarPayload {
|
||||
num: AMI_UUID_WORDS as u32,
|
||||
data_ptr: data_ptr as *mut u32,
|
||||
bar_idx: 0,
|
||||
offset: AMI_UUID_BAR_OFFSET,
|
||||
cap_override: true,
|
||||
};
|
||||
|
||||
tracing::trace!("AMI: Read request with following payload {payload:x?}");
|
||||
loop {
|
||||
let ret = unsafe { ami_bar_read(ami_fd, &mut payload) };
|
||||
match ret {
|
||||
Err(err) => {
|
||||
tracing::debug!("AMI: Read failed -> {err:?}");
|
||||
std::thread::sleep(self.retry_rate);
|
||||
}
|
||||
Ok(val) => {
|
||||
tracing::trace!("AMI: Read ack received {payload:x?} -> {val:?}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let uuid = unsafe { *Box::from_raw(data_ptr) };
|
||||
uuid.iter()
|
||||
.rev()
|
||||
.fold(String::new(), |acc, w| acc + &format!("{w:0>8x}"))
|
||||
}
|
||||
|
||||
/// Check if current ami version is compliant
|
||||
///
|
||||
/// For this purpose we use a regex.
|
||||
/// it's easy to expressed and understand breaking rules with it
|
||||
pub fn check_version() {
|
||||
pub fn check_version(amc_ver: &Version) -> Result<(), Box<dyn Error>> {
|
||||
// Check AMI version
|
||||
lazy_static! {
|
||||
static ref AMI_VERSION_RE: regex::Regex =
|
||||
@@ -81,7 +181,7 @@ impl AmiDriver {
|
||||
.write(false)
|
||||
.create(false)
|
||||
.open(AMI_VERSION_FILE)
|
||||
.unwrap();
|
||||
.map_err(|err| format!("Opening file {AMI_VERSION_FILE} failed: {err:?}"))?;
|
||||
|
||||
let ami_version = {
|
||||
let mut ver = String::new();
|
||||
@@ -93,10 +193,11 @@ impl AmiDriver {
|
||||
};
|
||||
|
||||
if !AMI_VERSION_RE.is_match(&ami_version) {
|
||||
panic!(
|
||||
return Err(format!(
|
||||
"Invalid ami version. Get {} expect something matching pattern {}",
|
||||
ami_version, AMI_VERSION_PATTERN
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
// Check HIS version
|
||||
@@ -114,23 +215,38 @@ impl AmiDriver {
|
||||
.write(false)
|
||||
.create(false)
|
||||
.open(his_version_file.expand())
|
||||
.unwrap();
|
||||
.unwrap_or_else(|err| {
|
||||
panic!("Opening file {} failed: {err:?}", his_version_file.expand())
|
||||
});
|
||||
|
||||
let his_version = {
|
||||
let mut ver = String::new();
|
||||
his_ver_f
|
||||
.read_to_string(&mut ver)
|
||||
.expect("Invalid HIS_VERSION string format");
|
||||
|
||||
.map_err(|e| format!("Invalid HIS_VERSION string format: {e:?}"))?;
|
||||
ver
|
||||
};
|
||||
|
||||
if !HIS_VERSION_RE.is_match(&his_version) {
|
||||
panic!(
|
||||
"Invalid his version. Get {} expect something matching pattern {}",
|
||||
his_version, HIS_VERSION_PATTERN
|
||||
let caps = HIS_VERSION_RE
|
||||
.captures(&his_version)
|
||||
.ok_or("Invalid his version format")?;
|
||||
let amc_major = caps["major"].parse::<u32>()?;
|
||||
let amc_minor = caps["minor"].parse::<u32>()?;
|
||||
if amc_major != amc_ver.major {
|
||||
return Err(format!(
|
||||
"Invalid his major version. Get {} expect {}",
|
||||
amc_major, amc_ver.major
|
||||
)
|
||||
.into());
|
||||
}
|
||||
if amc_minor != amc_ver.minor {
|
||||
return Err(format!(
|
||||
"Invalid his minor version. Get {} expect {}",
|
||||
amc_minor, amc_ver.minor
|
||||
)
|
||||
.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Issue read register request through AMI driver
|
||||
@@ -292,6 +408,40 @@ impl AmiDriver {
|
||||
// Define driver IOCTL command and associated payload -------------------------
|
||||
const AMI_IOC_MAGIC: u8 = b'a';
|
||||
|
||||
// Bar Read/Write command used for PCIe device probing ------------------------
|
||||
const AMI_BAR_READ_CMD: u8 = 1;
|
||||
// const AMI_BAR_WRITE_CMD: u8 = 2;
|
||||
|
||||
/// Payload used for PCI BAR registers read/write
|
||||
/// Args:
|
||||
/// * num: Number of BAR registers (to read or write).
|
||||
/// * data_ptr: Userspace address of data payload (read or write).
|
||||
/// * bar_idx: Bar number.
|
||||
/// * offset: Offset within BAR.
|
||||
/// * cap_override: Bypass permission checks. This may not apply to all IOCTL's.
|
||||
///
|
||||
/// Note:
|
||||
/// For reading a BAR, `addr` is the userspace address of a u32 buffer to be
|
||||
/// populated with data read from the BAR and `num` is the number of values to read.
|
||||
/// To write to a BAR, `addr` is the userspace address of the u32 buffer to
|
||||
/// write and `num` is the number of values to write.
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
struct AmiBarPayload {
|
||||
num: u32,
|
||||
data_ptr: *mut u32,
|
||||
bar_idx: u8,
|
||||
offset: u64,
|
||||
cap_override: bool,
|
||||
}
|
||||
nix::ioctl_readwrite!(ami_bar_read, AMI_IOC_MAGIC, AMI_BAR_READ_CMD, AmiBarPayload);
|
||||
// nix::ioctl_write_ptr!(
|
||||
// ami_bar_write,
|
||||
// AMI_IOC_MAGIC,
|
||||
// AMI_BAR_WRITE_CMD,
|
||||
// AmiBarPayload
|
||||
// );
|
||||
|
||||
// Peak/Poke command used for Read/Write in registers -------------------------
|
||||
const AMI_PEAK_CMD: u8 = 15;
|
||||
const AMI_POKE_CMD: u8 = 16;
|
||||
|
||||
@@ -5,17 +5,28 @@
|
||||
//! * Data xfer -> QDMA
|
||||
|
||||
use crate::ffi;
|
||||
use crate::prelude::ShellString;
|
||||
|
||||
use std::error::Error;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::process::Command;
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
mod ami;
|
||||
use ami::AmiDriver;
|
||||
mod pdi;
|
||||
pub use pdi::HpuV80Pdi;
|
||||
|
||||
mod mem_alloc;
|
||||
use mem_alloc::{MemAlloc, MemChunk};
|
||||
|
||||
mod qdma;
|
||||
use qdma::QdmaDriver;
|
||||
use rand::Rng;
|
||||
|
||||
const DMA_XFER_ALIGN: usize = 4096_usize;
|
||||
|
||||
pub struct HpuHw {
|
||||
pub(super) ami: AmiDriver,
|
||||
@@ -25,16 +36,312 @@ pub struct HpuHw {
|
||||
|
||||
impl HpuHw {
|
||||
/// Handle ffi instantiation
|
||||
/// Instantiate current HW and check uuid. If it match with targeted one continue,
|
||||
/// otherwise reload Pdi
|
||||
#[inline(always)]
|
||||
pub fn new_hpu_hw(
|
||||
ami_id: usize,
|
||||
id: u32,
|
||||
hpu_path: &str,
|
||||
ami_path: &str,
|
||||
ami_retry: std::time::Duration,
|
||||
h2c_path: &str,
|
||||
c2h_path: &str,
|
||||
) -> HpuHw {
|
||||
// Load Pdi archive
|
||||
let hpu_pdi = HpuV80Pdi::from_bincode(hpu_path)
|
||||
.unwrap_or_else(|err| panic!("Invalid \'.hpu\' {hpu_path:?}: {err}"));
|
||||
|
||||
// Try current hw and fallback to a fresh reload
|
||||
Self::try_current_hw(id, &hpu_pdi, ami_retry, h2c_path, c2h_path).unwrap_or_else(|err| {
|
||||
tracing::warn!("Loading current HW failed with {err:?}. Will do a fresh reload");
|
||||
Self::reload_hw(id, &hpu_pdi, ami_path, ami_retry, h2c_path, c2h_path)
|
||||
})
|
||||
}
|
||||
|
||||
/// Load a Pdi in the FPGA
|
||||
/// NB: This procedure required unload of Qdma/Ami driver and thus couldn't be directly
|
||||
/// implemented in the AMI
|
||||
fn try_current_hw(
|
||||
id: u32,
|
||||
pdi: &HpuV80Pdi,
|
||||
ami_retry: std::time::Duration,
|
||||
h2c_path: &str,
|
||||
c2h_path: &str,
|
||||
) -> Result<Self, Box<dyn Error>> {
|
||||
let ami = AmiDriver::new(id, &pdi.metadata.amc.his_version, ami_retry)?;
|
||||
let qdma = QdmaDriver::new(h2c_path, c2h_path)?;
|
||||
|
||||
let hw = Self {
|
||||
ami,
|
||||
qdma: Arc::new(Mutex::new(qdma)),
|
||||
allocator: None,
|
||||
};
|
||||
|
||||
let current_uuid = hw.ami.uuid();
|
||||
if pdi.metadata.bitstream.uuid == current_uuid {
|
||||
let uuid = pdi::V80Uuid::from_str(&pdi.metadata.bitstream.uuid)
|
||||
.expect("Invalid UUID format in pdi");
|
||||
tracing::info!("Current pdi -> [\n{uuid}]");
|
||||
Ok(hw)
|
||||
} else {
|
||||
Err(format!(
|
||||
"UUID mismatch loaded {:?} expected {:?}",
|
||||
current_uuid, pdi.metadata.bitstream.uuid
|
||||
)
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Load a Pdi in the FPGA
|
||||
/// NB: This procedure required unload of Qdma/Ami driver and thus couldn't be directly
|
||||
/// implemented in the AMI
|
||||
fn reload_hw(
|
||||
id: u32,
|
||||
pdi: &HpuV80Pdi,
|
||||
ami_path: &str,
|
||||
ami_retry: std::time::Duration,
|
||||
h2c_path: &str,
|
||||
c2h_path: &str,
|
||||
) -> Self {
|
||||
tracing::warn!("FPGA reload procedure. Following step require sudo rights to handle modules loading and pcie subsystem configuration.");
|
||||
let uuid = pdi::V80Uuid::from_str(&pdi.metadata.bitstream.uuid)
|
||||
.expect("Invalid UUID format in pdi");
|
||||
tracing::info!("Load pdi -> [\n{uuid}]");
|
||||
tracing::debug!("Unload drivers ami/qdma_pf");
|
||||
//Prerequist. Enforce that ami/qdma driver are unloaded
|
||||
// NB: Separate the call to match sudoers rules
|
||||
let _ = Command::new("sudo")
|
||||
.arg("/usr/sbin/rmmod")
|
||||
.arg("ami")
|
||||
.status();
|
||||
let _ = Command::new("sudo")
|
||||
.arg("/usr/sbin/rmmod")
|
||||
.arg("qdma_pf")
|
||||
.status();
|
||||
|
||||
// 1. Load PDI stage one through JTAG ----------------------------------
|
||||
// -> Use Xilinx hw_manager. Currently used through vivado for ease setup.
|
||||
// hw manager expect stage 1 in a file, thus start by expanding the stg1_pdi in a tmp file
|
||||
tracing::debug!("Load stage1 through JTAG");
|
||||
let pdi_stg1_tmp = format!(
|
||||
"hpu_stg1_{}.pdi",
|
||||
rand::thread_rng()
|
||||
.sample_iter(rand::distributions::Alphanumeric)
|
||||
.take(5)
|
||||
.map(char::from)
|
||||
.collect::<String>()
|
||||
);
|
||||
let tmp_dir = std::env::temp_dir();
|
||||
let tmp_dir_str = tmp_dir.to_str().expect("TEMP_DIR is not a valid UTF-8");
|
||||
|
||||
// Write the binary data
|
||||
HpuV80Pdi::write_to_path(tmp_dir_str, &pdi_stg1_tmp, &pdi.pdi_stg1_bin)
|
||||
.expect("Error while expanding stg1 pdi on filesystem");
|
||||
|
||||
let hw_monitor =
|
||||
Command::new(ShellString::new("${XILINX_VIVADO}/bin/vivado".to_string()).expand())
|
||||
.arg("-mode")
|
||||
.arg("batch")
|
||||
.arg("-source")
|
||||
.arg(
|
||||
ShellString::new("${HPU_BACKEND_DIR}/scripts/pdi_jtag.tcl".to_string())
|
||||
.expand(),
|
||||
)
|
||||
.arg("-tclargs")
|
||||
.arg(format!("{}/{}", tmp_dir_str, &pdi_stg1_tmp))
|
||||
.output();
|
||||
tracing::debug!("Stage1 loaded: {hw_monitor:?}");
|
||||
|
||||
// Update right on V80 pcie subsystem
|
||||
Command::new("sudo")
|
||||
.arg(
|
||||
ShellString::new("${HPU_BACKEND_DIR}/scripts/v80-pcie-perms.sh".to_string())
|
||||
.expand(),
|
||||
)
|
||||
.status()
|
||||
.expect("Unable to update v80 pcie sysfs right");
|
||||
|
||||
tracing::debug!(" Updating Pcie physical functions status");
|
||||
let rm_pf0 = OpenOptions::new()
|
||||
.write(true)
|
||||
.open(
|
||||
ShellString::new(
|
||||
"/sys/bus/pci/devices/0000:${V80_PCIE_DEV}:00.0/remove".to_string(),
|
||||
)
|
||||
.expand(),
|
||||
)
|
||||
.ok();
|
||||
if let Some(mut pf0) = rm_pf0 {
|
||||
pf0.write_all(b"1\n")
|
||||
.expect("Unable to triggered a remove of pci pf0");
|
||||
} else {
|
||||
tracing::debug!("Pcie PF0 not present.");
|
||||
}
|
||||
|
||||
OpenOptions::new()
|
||||
.write(true)
|
||||
.open(
|
||||
ShellString::new(
|
||||
"/sys/bus/pci/devices/0000:${V80_PCIE_DEV}:00.1/remove".to_string(),
|
||||
)
|
||||
.expand(),
|
||||
)
|
||||
.expect("Unable to open pci remove cmd file")
|
||||
.write_all(b"1\n")
|
||||
.expect("Unable to triggered a remove of pci pf1");
|
||||
OpenOptions::new()
|
||||
.write(true)
|
||||
.open("/sys/bus/pci/rescan")
|
||||
.expect("Unable to open pci rescan cmd file")
|
||||
.write_all(b"1\n")
|
||||
.expect("Unable to triggered a pci rescan");
|
||||
|
||||
// Update right on V80 pcie subsystem
|
||||
// NB: sysfs is recreated upon rescan
|
||||
Command::new("sudo")
|
||||
.arg(
|
||||
ShellString::new("${HPU_BACKEND_DIR}/scripts/v80-pcie-perms.sh".to_string())
|
||||
.expand(),
|
||||
)
|
||||
.status()
|
||||
.expect("Unable to update v80 pcie sysfs right");
|
||||
|
||||
// 2. Load PDI stage two through QDMA ----------------------------------
|
||||
tracing::debug!("Load stage2 through Qdma");
|
||||
// Create h2c queue at idx 0
|
||||
|
||||
tracing::debug!("Initializing queues");
|
||||
OpenOptions::new()
|
||||
.write(true)
|
||||
.open(
|
||||
ShellString::new(
|
||||
"/sys/bus/pci/devices/0000:${V80_PCIE_DEV}:00.1/qdma/qmax".to_string(),
|
||||
)
|
||||
.expand(),
|
||||
)
|
||||
.expect("Unable to open qdma qmax cmd file")
|
||||
.write_all(b"100\n")
|
||||
.expect("Unable to configure Qdma max queues");
|
||||
Command::new("dma-ctl")
|
||||
.arg("qdma01001")
|
||||
.arg("q")
|
||||
.arg("add")
|
||||
.arg("idx")
|
||||
.arg("0")
|
||||
.arg("dir")
|
||||
.arg("h2c")
|
||||
.status()
|
||||
.expect("Unable to create Qdma queue0");
|
||||
Command::new("dma-ctl")
|
||||
.arg("qdma01001")
|
||||
.arg("q")
|
||||
.arg("start")
|
||||
.arg("idx")
|
||||
.arg("0")
|
||||
.arg("dir")
|
||||
.arg("h2c")
|
||||
.arg("aperture_sz")
|
||||
.arg(DMA_XFER_ALIGN.to_string())
|
||||
.status()
|
||||
.expect("Unable to start Qdma queue0");
|
||||
|
||||
tracing::debug!("Uploading stage2: [{} bytes]", pdi.pdi_stg2_bin.len());
|
||||
// NB: Dma required same alignment as aperture.
|
||||
let stg2_aligned = {
|
||||
let len = pdi.pdi_stg2_bin.len();
|
||||
let layout = std::alloc::Layout::from_size_align(len, DMA_XFER_ALIGN)
|
||||
.expect("Invalid layout definition for stg2 aligned buffer");
|
||||
let raw_ptr = unsafe { std::alloc::alloc(layout) };
|
||||
let data = unsafe { std::slice::from_raw_parts_mut(raw_ptr, len) };
|
||||
data.copy_from_slice(pdi.pdi_stg2_bin.as_slice());
|
||||
data
|
||||
};
|
||||
|
||||
let qdma_h2c = OpenOptions::new()
|
||||
.read(false)
|
||||
.write(true)
|
||||
.create(false)
|
||||
.open("/dev/qdma01001-MM-0")
|
||||
.expect("Unable to open Qdma queue 0");
|
||||
let ret = nix::sys::uio::pwrite(&qdma_h2c, stg2_aligned, 0x000102100000_i64)
|
||||
.expect("Unable to write stage2 pdi");
|
||||
tracing::debug!("QDMA written {ret} bytes to device");
|
||||
// Properly release custom allocated memory
|
||||
unsafe { nix::libc::free(stg2_aligned.as_mut_ptr() as *mut _) };
|
||||
|
||||
tracing::debug!(" Updating Pcie physical functions 0 status");
|
||||
OpenOptions::new()
|
||||
.write(true)
|
||||
.open("/sys/bus/pci/devices/0000:01:00.0/remove")
|
||||
.expect("Unable to open pci remove cmd file")
|
||||
.write_all(b"1\n")
|
||||
.expect("Unable to triggered a remove of pci pf0");
|
||||
OpenOptions::new()
|
||||
.write(true)
|
||||
.open("/sys/bus/pci/rescan")
|
||||
.expect("Unable to open pci rescan cmd file")
|
||||
.write_all(b"1\n")
|
||||
.expect("Unable to triggered a pci rescan");
|
||||
|
||||
// 3. Create user queues ----------------------------------------------
|
||||
Command::new("dma-ctl")
|
||||
.arg("qdma01001")
|
||||
.arg("q")
|
||||
.arg("add")
|
||||
.arg("idx")
|
||||
.arg("1")
|
||||
.arg("dir")
|
||||
.arg("h2c")
|
||||
.status()
|
||||
.expect("Unable to create Qdma queue1");
|
||||
Command::new("dma-ctl")
|
||||
.arg("qdma01001")
|
||||
.arg("q")
|
||||
.arg("start")
|
||||
.arg("idx")
|
||||
.arg("1")
|
||||
.arg("dir")
|
||||
.arg("h2c")
|
||||
.status()
|
||||
.expect("Unable to start Qdma queue1");
|
||||
|
||||
Command::new("dma-ctl")
|
||||
.arg("qdma01001")
|
||||
.arg("q")
|
||||
.arg("add")
|
||||
.arg("idx")
|
||||
.arg("2")
|
||||
.arg("dir")
|
||||
.arg("c2h")
|
||||
.status()
|
||||
.expect("Unable to create Qdma queue2");
|
||||
Command::new("dma-ctl")
|
||||
.arg("qdma01001")
|
||||
.arg("q")
|
||||
.arg("start")
|
||||
.arg("idx")
|
||||
.arg("2")
|
||||
.arg("dir")
|
||||
.arg("c2h")
|
||||
.status()
|
||||
.expect("Unable to start Qdma queue2");
|
||||
|
||||
// 4. load ami kernel module ------------------------------------------
|
||||
// Ami is to tight to amc version and thus bundle in .hpu_bin archive
|
||||
tracing::debug!("Load ami kernel module");
|
||||
Command::new("sudo")
|
||||
.arg("insmod")
|
||||
.arg(ami_path)
|
||||
.status()
|
||||
.expect("Unable to load ami.ko");
|
||||
|
||||
Self {
|
||||
ami: AmiDriver::new(ami_id, ami_retry),
|
||||
qdma: Arc::new(Mutex::new(QdmaDriver::new(h2c_path, c2h_path))),
|
||||
ami: AmiDriver::new(id, &pdi.metadata.amc.his_version, ami_retry)
|
||||
.expect("Unable to create AmiDriver after a fresh pdi load on device {id}"),
|
||||
qdma: Arc::new(Mutex::new(
|
||||
QdmaDriver::new(h2c_path, c2h_path)
|
||||
.expect("Unable to create QdmaDriver after a fresh pdi load"),
|
||||
)),
|
||||
allocator: None,
|
||||
}
|
||||
}
|
||||
|
||||
132
backends/tfhe-hpu-backend/src/ffi/v80/pdi/metadata.rs
Normal file
132
backends/tfhe-hpu-backend/src/ffi/v80/pdi/metadata.rs
Normal file
@@ -0,0 +1,132 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::error::Error;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{BufWriter, Write};
|
||||
use std::path::Path;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
pub struct Version {
|
||||
pub major: u32,
|
||||
pub minor: u32,
|
||||
pub flavor: Option<String>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Version {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "v{}.{}", self.major, self.minor)?;
|
||||
if let Some(flavor) = &self.flavor {
|
||||
write!(f, "-{}", flavor)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for Version {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
lazy_static! {
|
||||
static ref VER_ARG_RE: regex::Regex =
|
||||
regex::Regex::new(r"v(?<major>\d+).(?<minor>\d+)(-(?<flavor>.+))?")
|
||||
.expect("Invalid regex");
|
||||
}
|
||||
if let Some(caps) = VER_ARG_RE.captures(s) {
|
||||
let major = caps["major"]
|
||||
.trim_ascii()
|
||||
.parse::<u32>()
|
||||
.expect("Invalid major format. Must be an integer");
|
||||
let minor = caps["minor"]
|
||||
.trim_ascii()
|
||||
.parse::<u32>()
|
||||
.expect("Invalid minor format. Must be an integer");
|
||||
let flavor = caps
|
||||
.name("flavor")
|
||||
.map(|flavor| flavor.as_str().to_string());
|
||||
Ok(Self {
|
||||
major,
|
||||
minor,
|
||||
flavor,
|
||||
})
|
||||
} else {
|
||||
Err(
|
||||
"Invalid version format, expect v{major}.{minor}[-{flavor}]. Where major/minor are integer and flavor String.".to_string(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct BitstreamMetadata {
|
||||
pub uuid: String,
|
||||
pub wns_ps: f64,
|
||||
pub tns_ps: f64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct AmcMetadata {
|
||||
pub his_version: Version,
|
||||
}
|
||||
|
||||
pub const HPU_METADATA_VERSION: Version = Version {
|
||||
major: 1,
|
||||
minor: 0,
|
||||
flavor: None,
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Metadata {
|
||||
pub version: Version,
|
||||
pub pdi_stg1_file: String,
|
||||
pub pdi_stg2_file: String,
|
||||
pub xsa_file: String,
|
||||
pub elf_file: String,
|
||||
pub amc: AmcMetadata,
|
||||
pub bitstream: BitstreamMetadata,
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
#[allow(unused)]
|
||||
/// Provide Serde mechanisms from TOML file
|
||||
pub fn from_toml(file: &str) -> Result<Self, Box<dyn Error>> {
|
||||
let file_str = std::fs::read_to_string(file).inspect_err(|_e| {
|
||||
eprintln!("Read error with file {file}");
|
||||
})?;
|
||||
let res = toml::from_str(&file_str)?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
/// Provide Serde mechanisms to TOML file
|
||||
pub fn to_toml(&self, file: &str) -> Result<(), Box<dyn Error>> {
|
||||
// Open file
|
||||
// Create path
|
||||
let path = Path::new(&file);
|
||||
if let Some(dir_p) = path.parent() {
|
||||
std::fs::create_dir_all(dir_p).unwrap();
|
||||
}
|
||||
|
||||
// Open file
|
||||
let mut wr_f = BufWriter::new(
|
||||
OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(path)
|
||||
.inspect_err(|_e| {
|
||||
eprintln!("Open error with {path:?}");
|
||||
})?,
|
||||
);
|
||||
|
||||
// Convert in toml str and write into file
|
||||
let toml_str = toml::to_string_pretty(&self).inspect_err(|_e| {
|
||||
eprintln!("Serialize error with {self:?}");
|
||||
})?;
|
||||
|
||||
wr_f.write_all(toml_str.as_bytes()).inspect_err(|_e| {
|
||||
eprintln!("Write error with {wr_f:?}");
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
156
backends/tfhe-hpu-backend/src/ffi/v80/pdi/mod.rs
Normal file
156
backends/tfhe-hpu-backend/src/ffi/v80/pdi/mod.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::error::Error;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{BufReader, BufWriter, Read, Seek, Write};
|
||||
use std::path::Path;
|
||||
|
||||
pub mod metadata;
|
||||
use metadata::{Metadata, HPU_METADATA_VERSION};
|
||||
pub mod uuid;
|
||||
pub use uuid::V80Uuid;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct HpuV80Pdi {
|
||||
pub metadata: Metadata,
|
||||
pub pdi_stg1_bin: Vec<u8>,
|
||||
pub pdi_stg2_bin: Vec<u8>,
|
||||
pub xsa_bin: Vec<u8>,
|
||||
pub elf_bin: Vec<u8>,
|
||||
}
|
||||
|
||||
impl HpuV80Pdi {
|
||||
#[allow(unused)]
|
||||
/// Utility function to read stream of data from file with proper error display
|
||||
pub(crate) fn read_from_path(path: &str, file: &str) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
let file_p = Path::new(path).join(file);
|
||||
let rd_f = BufReader::new(OpenOptions::new().read(true).open(&file_p).inspect_err(
|
||||
|_e| {
|
||||
eprintln!("OpenOptions error with {file_p:?}");
|
||||
},
|
||||
)?);
|
||||
|
||||
let data = rd_f.bytes().collect::<Result<Vec<_>, _>>()?;
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
/// Utility function to write stream of data into file with proper error display
|
||||
pub(crate) fn write_to_path(path: &str, file: &str, data: &[u8]) -> Result<(), Box<dyn Error>> {
|
||||
let file_p = Path::new(path).join(file);
|
||||
|
||||
// Enforce that parent folder exist and open file
|
||||
if let Some(dir_p) = Path::new(&file_p).parent() {
|
||||
std::fs::create_dir_all(dir_p).unwrap();
|
||||
}
|
||||
let mut wr_f = BufWriter::new(
|
||||
OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(&file_p)
|
||||
.inspect_err(|_e| {
|
||||
eprintln!("OpenOptions error with {file_p:?}");
|
||||
})?,
|
||||
);
|
||||
wr_f.write_all(data)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl HpuV80Pdi {
|
||||
#[allow(unused)]
|
||||
/// Construct HpuV80Pdi from a folder with discrete files
|
||||
pub fn from_folder(folder_path: &str) -> Result<Self, Box<dyn Error>> {
|
||||
let metadata_path = Path::new(folder_path).join("metadata.toml");
|
||||
let metadata = Metadata::from_toml(
|
||||
metadata_path
|
||||
.to_str()
|
||||
.expect("Invalid unicode path {metadata_path:?}"),
|
||||
)?;
|
||||
|
||||
// Read the binary files
|
||||
let pdi_stg1_bin = Self::read_from_path(folder_path, &metadata.pdi_stg1_file)?;
|
||||
let pdi_stg2_bin = Self::read_from_path(folder_path, &metadata.pdi_stg2_file)?;
|
||||
let xsa_bin = Self::read_from_path(folder_path, &metadata.xsa_file)?;
|
||||
let elf_bin = Self::read_from_path(folder_path, &metadata.elf_file)?;
|
||||
|
||||
Ok(HpuV80Pdi {
|
||||
metadata,
|
||||
pdi_stg1_bin,
|
||||
pdi_stg2_bin,
|
||||
xsa_bin,
|
||||
elf_bin,
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
/// Deconstruct HpuV80Pdi into a folder with discret files
|
||||
pub fn to_folder(&self, folder_path: &str) -> Result<(), Box<dyn Error>> {
|
||||
let metadata_path = Path::new(folder_path).join("metadata.toml");
|
||||
self.metadata.to_toml(
|
||||
metadata_path
|
||||
.to_str()
|
||||
.expect("Invalid unicode in path {metadata_path:?}"),
|
||||
)?;
|
||||
// Write the binary data
|
||||
Self::write_to_path(
|
||||
folder_path,
|
||||
&self.metadata.pdi_stg1_file,
|
||||
&self.pdi_stg1_bin,
|
||||
)?;
|
||||
Self::write_to_path(
|
||||
folder_path,
|
||||
&self.metadata.pdi_stg2_file,
|
||||
&self.pdi_stg2_bin,
|
||||
)?;
|
||||
Self::write_to_path(folder_path, &self.metadata.xsa_file, &self.xsa_bin)?;
|
||||
Self::write_to_path(folder_path, &self.metadata.elf_file, &self.elf_bin)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
/// Deserialize HpuV80Pdi from a bin file
|
||||
pub fn from_bincode(file_path: &str) -> Result<Self, Box<dyn Error>> {
|
||||
let mut rd_f = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(file_path)
|
||||
.inspect_err(|_e| {
|
||||
eprintln!("OpenOptions error with {file_path}");
|
||||
})?;
|
||||
|
||||
let meta_version: metadata::Version = bincode::deserialize_from(&rd_f)?;
|
||||
if meta_version != HPU_METADATA_VERSION {
|
||||
return Err(format!(
|
||||
"Archive use version \"{meta_version}\", Sw expect version \"{HPU_METADATA_VERSION}\""
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
// Start from beginning and use bufReader for performance
|
||||
rd_f.seek(std::io::SeekFrom::Start(0));
|
||||
let rd_bfr = BufReader::new(rd_f);
|
||||
let hpu_pdi = bincode::deserialize_from(rd_bfr)?;
|
||||
Ok(hpu_pdi)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
/// Serialize HpuV80Pdi into a bin file
|
||||
pub fn to_bincode(&self, file_path: &str) -> Result<(), Box<dyn Error>> {
|
||||
// Enforce that parent folder exist and open file
|
||||
if let Some(dir_p) = Path::new(&file_path).parent() {
|
||||
std::fs::create_dir_all(dir_p).unwrap();
|
||||
}
|
||||
let wr_f = BufWriter::new(
|
||||
OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(file_path)
|
||||
.inspect_err(|_e| {
|
||||
eprintln!("OpenOptions error with {file_path}");
|
||||
})?,
|
||||
);
|
||||
bincode::serialize_into(wr_f, self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
287
backends/tfhe-hpu-backend/src/ffi/v80/pdi/uuid.rs
Normal file
287
backends/tfhe-hpu-backend/src/ffi/v80/pdi/uuid.rs
Normal file
@@ -0,0 +1,287 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
pub const UNUSED_FIELD_LEN: usize = 5;
|
||||
pub const HASH_FIELD_LEN: usize = 7;
|
||||
pub const FREQ_FIELD_LEN: usize = 4;
|
||||
pub const VERSION_FIELD_LEN: usize = 2;
|
||||
pub const NTT_ARCH_FIELD_LEN: usize = 1;
|
||||
pub const PSI_FIELD_LEN: usize = 1;
|
||||
pub const HOST_FIELD_LEN: usize = 1;
|
||||
pub const USER_FIELD_LEN: usize = 1;
|
||||
pub const DATE_FIELD_LEN: usize = 10;
|
||||
pub const UUID_LEN: usize = UNUSED_FIELD_LEN
|
||||
+ HASH_FIELD_LEN
|
||||
+ FREQ_FIELD_LEN
|
||||
+ VERSION_FIELD_LEN
|
||||
+ NTT_ARCH_FIELD_LEN
|
||||
+ PSI_FIELD_LEN
|
||||
+ HOST_FIELD_LEN
|
||||
+ USER_FIELD_LEN
|
||||
+ DATE_FIELD_LEN;
|
||||
|
||||
pub(crate) const AMI_UUID_WORDS: usize = 4;
|
||||
|
||||
fn from_readable_hex(s: &str) -> usize {
|
||||
s.chars()
|
||||
.flat_map(|c| c.to_digit(10))
|
||||
.map(|v| v as usize)
|
||||
.fold(0, |acc, v| 10 * acc + v)
|
||||
}
|
||||
|
||||
struct Hash(usize);
|
||||
impl std::fmt::Display for Hash {
|
||||
// Hash is on 7hex digits
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:7>0x}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Hash {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if s.len() != HASH_FIELD_LEN {
|
||||
Err("Hash sub-field: Invalid length".to_string())
|
||||
} else {
|
||||
Ok(Self(
|
||||
usize::from_str_radix(s, 16).map_err(|err| format!("Hash sub-field: {err:?}"))?,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Freq(usize);
|
||||
|
||||
impl std::fmt::Display for Freq {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Freq_MHz: {}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
// NB: Freq is displayed on 4 hex digits in human readable form
|
||||
impl FromStr for Freq {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if s.len() != FREQ_FIELD_LEN {
|
||||
Err("Freq sub-field: Invalid length".to_string())
|
||||
} else {
|
||||
Ok(Self(from_readable_hex(s)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Version {
|
||||
major: usize,
|
||||
minor: usize,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Version {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "v{}.{}", self.major, self.minor)
|
||||
}
|
||||
}
|
||||
|
||||
// NB: Freq is displayed on 4 hex digits in human readable form
|
||||
impl FromStr for Version {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if s.len() != VERSION_FIELD_LEN {
|
||||
Err("Version sub-field: Invalid length".to_string())
|
||||
} else {
|
||||
let minor = from_readable_hex(&s[0..=0]);
|
||||
let major = from_readable_hex(&s[1..=1]);
|
||||
Ok(Self { major, minor })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum NttArch {
|
||||
Unfold = 4,
|
||||
GF64 = 5,
|
||||
}
|
||||
|
||||
impl FromStr for NttArch {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if s.len() != NTT_ARCH_FIELD_LEN {
|
||||
Err("NttArch sub-field: Invalid length".to_string())
|
||||
} else {
|
||||
let val = from_readable_hex(s);
|
||||
match val {
|
||||
0x4 => Ok(NttArch::Unfold),
|
||||
0x5 => Ok(NttArch::GF64),
|
||||
_ => Err(format!("NttArch sub-field: Invalid value {val}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Host {
|
||||
SrvZama = 1,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl FromStr for Host {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if s.len() != PSI_FIELD_LEN {
|
||||
Err("Host sub-field: Invalid length".to_string())
|
||||
} else {
|
||||
let val = from_readable_hex(s);
|
||||
match val {
|
||||
0x1 => Ok(Host::SrvZama),
|
||||
_ => Ok(Host::Unknown),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Psi {
|
||||
Psi16 = 0,
|
||||
Psi32 = 1,
|
||||
Psi64 = 2,
|
||||
}
|
||||
impl FromStr for Psi {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if s.len() != PSI_FIELD_LEN {
|
||||
Err("Psi sub-field: Invalid length".to_string())
|
||||
} else {
|
||||
let val = from_readable_hex(s);
|
||||
match val {
|
||||
0x0 => Ok(Psi::Psi16),
|
||||
0x1 => Ok(Psi::Psi32),
|
||||
0x2 => Ok(Psi::Psi64),
|
||||
_ => Err(format!("Psi sub-field: Invalid value {val}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct User(usize);
|
||||
|
||||
impl FromStr for User {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if s.len() != USER_FIELD_LEN {
|
||||
Err("User sub-field: Invalid length".to_string())
|
||||
} else {
|
||||
Ok(Self(from_readable_hex(s)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for User {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "0x{:x}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Date {
|
||||
year: usize,
|
||||
month: usize,
|
||||
day: usize,
|
||||
hour: usize,
|
||||
min: usize,
|
||||
}
|
||||
|
||||
impl FromStr for Date {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if s.len() != DATE_FIELD_LEN {
|
||||
Err("Date sub-field: Invalid length".to_string())
|
||||
} else {
|
||||
Ok(Self {
|
||||
year: from_readable_hex(&s[0..2]),
|
||||
month: from_readable_hex(&s[2..4]),
|
||||
day: from_readable_hex(&s[4..6]),
|
||||
hour: from_readable_hex(&s[6..8]),
|
||||
min: from_readable_hex(&s[8..10]),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Date {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{:0>2}/{:0>2}/{:0>2}::{:0>2}h{:0>2}",
|
||||
self.year, self.month, self.day, self.hour, self.min
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct V80Uuid {
|
||||
hash: Hash,
|
||||
freq: Freq,
|
||||
version: Version,
|
||||
arch: NttArch,
|
||||
psi: Psi,
|
||||
host: Host,
|
||||
user: User,
|
||||
date: Date,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for V80Uuid {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "hash: {}", self.hash)?;
|
||||
writeln!(f, "freq: {}", self.freq)?;
|
||||
writeln!(f, "version: {}", self.version)?;
|
||||
writeln!(f, "arch: {:?}", self.arch)?;
|
||||
writeln!(f, "psi: {:?}", self.psi)?;
|
||||
writeln!(f, "host: {:?}", self.host)?;
|
||||
writeln!(f, "user: {}", self.user)?;
|
||||
writeln!(f, "date: {}", self.date)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for V80Uuid {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if s.len() != UUID_LEN {
|
||||
Err("UUID: Invalid length".to_string())
|
||||
} else {
|
||||
let mut idx = UNUSED_FIELD_LEN;
|
||||
let hash = Hash::from_str(&s[idx..idx + HASH_FIELD_LEN])?;
|
||||
idx += HASH_FIELD_LEN;
|
||||
let freq = Freq::from_str(&s[idx..idx + FREQ_FIELD_LEN])?;
|
||||
idx += FREQ_FIELD_LEN;
|
||||
let version = Version::from_str(&s[idx..idx + VERSION_FIELD_LEN])?;
|
||||
idx += VERSION_FIELD_LEN;
|
||||
let arch = NttArch::from_str(&s[idx..idx + NTT_ARCH_FIELD_LEN])?;
|
||||
idx += NTT_ARCH_FIELD_LEN;
|
||||
let psi = Psi::from_str(&s[idx..idx + PSI_FIELD_LEN])?;
|
||||
idx += PSI_FIELD_LEN;
|
||||
let host = Host::from_str(&s[idx..idx + HOST_FIELD_LEN])?;
|
||||
idx += HOST_FIELD_LEN;
|
||||
let user = User::from_str(&s[idx..idx + USER_FIELD_LEN])?;
|
||||
idx += USER_FIELD_LEN;
|
||||
let date = Date::from_str(&s[idx..idx + DATE_FIELD_LEN])?;
|
||||
|
||||
Ok(Self {
|
||||
hash,
|
||||
freq,
|
||||
version,
|
||||
arch,
|
||||
psi,
|
||||
host,
|
||||
user,
|
||||
date,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
//! ```
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use std::error::Error;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::Read;
|
||||
|
||||
@@ -35,16 +36,15 @@ pub(crate) struct QdmaDriver {
|
||||
}
|
||||
|
||||
impl QdmaDriver {
|
||||
pub fn new(h2c_path: &str, c2h_path: &str) -> Self {
|
||||
Self::check_version().unwrap();
|
||||
|
||||
pub fn new(h2c_path: &str, c2h_path: &str) -> Result<Self, Box<dyn Error>> {
|
||||
Self::check_version()?;
|
||||
// Open HostToCard xfer file
|
||||
let qdma_h2c = OpenOptions::new()
|
||||
.read(false)
|
||||
.write(true)
|
||||
.create(false)
|
||||
.open(h2c_path)
|
||||
.unwrap_or_else(|e| panic!("Invalid qdma_h2c path: {h2c_path} -> {e}. Check queue initialization and configuration."));
|
||||
.map_err(|err| format!("Opening file {h2c_path} failed: {err:?}"))?;
|
||||
|
||||
// Open CardToHost xfer file
|
||||
let qdma_c2h = OpenOptions::new()
|
||||
@@ -52,16 +52,16 @@ impl QdmaDriver {
|
||||
.write(false)
|
||||
.create(false)
|
||||
.open(c2h_path)
|
||||
.unwrap_or_else(|e| panic!("Invalid qdma_c2h path: {c2h_path} -> {e}. Check queue initialization and configuration."));
|
||||
.map_err(|err| format!("Opening file {c2h_path} failed: {err:?}"))?;
|
||||
|
||||
Self { qdma_h2c, qdma_c2h }
|
||||
Ok(Self { qdma_h2c, qdma_c2h })
|
||||
}
|
||||
|
||||
/// Check if current qdma version is compliant
|
||||
///
|
||||
/// For this purpose we use a regex.
|
||||
/// it's easy to expressed and understand breaking rules with it
|
||||
pub fn check_version() -> Result<(), String> {
|
||||
pub fn check_version() -> Result<(), Box<dyn Error>> {
|
||||
lazy_static! {
|
||||
static ref QDMA_VERSION_RE: regex::Regex =
|
||||
regex::Regex::new(QDMA_VERSION_PATTERN).expect("Invalid regex");
|
||||
@@ -73,7 +73,7 @@ impl QdmaDriver {
|
||||
.write(false)
|
||||
.create(false)
|
||||
.open(QDMA_VERSION_FILE)
|
||||
.unwrap();
|
||||
.map_err(|err| format!("Opening file {QDMA_VERSION_FILE} failed: {err:?}"))?;
|
||||
|
||||
let qdma_version = {
|
||||
let mut ver = String::new();
|
||||
@@ -90,7 +90,8 @@ impl QdmaDriver {
|
||||
Err(format!(
|
||||
"Invalid qdma version. Get {} expect something matching pattern {}",
|
||||
qdma_version, QDMA_VERSION_PATTERN
|
||||
))
|
||||
)
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,9 @@ impl std::str::FromStr for ShellString {
|
||||
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
|
||||
pub enum FFIMode {
|
||||
V80 {
|
||||
ami_id: usize,
|
||||
id: u32,
|
||||
hpu_path: ShellString,
|
||||
ami_path: ShellString,
|
||||
qdma_h2c: ShellString,
|
||||
qdma_c2h: ShellString,
|
||||
},
|
||||
|
||||
36
backends/tfhe-hpu-backend/src/utils/pdi_mgmt.rs
Normal file
36
backends/tfhe-hpu-backend/src/utils/pdi_mgmt.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use clap::{Parser, Subcommand};
|
||||
use std::error::Error;
|
||||
use tfhe_hpu_backend::ffi::HpuV80Pdi;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "Pdi Management. Enable Packing/Unpacking of Hpu Pdi")]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
Pack { from_path: String, to_file: String },
|
||||
Unpack { from_file: String, to_path: String },
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
match cli.command {
|
||||
Commands::Pack { from_path, to_file } => {
|
||||
let hpu_pdi = HpuV80Pdi::from_folder(&from_path)?;
|
||||
hpu_pdi.to_bincode(&to_file)?;
|
||||
|
||||
println!("Successfully packed folder {from_path} into {to_file}.");
|
||||
}
|
||||
Commands::Unpack { from_file, to_path } => {
|
||||
let hpu_pdi = HpuV80Pdi::from_bincode(&from_file)?;
|
||||
hpu_pdi.to_folder(&to_path)?;
|
||||
println!("Successfully unpacked file {from_file} into {to_path} folder.");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
41
setup_hpu.sh
41
setup_hpu.sh
@@ -24,12 +24,15 @@ else
|
||||
V80_PCIE_DEV="${DEVICE[0]%%:*}"
|
||||
fi
|
||||
|
||||
# Default Qdma init
|
||||
V80_QDMA_INIT=false
|
||||
# V80 bitstream refresh rely on XilinxVivado tools
|
||||
XILINX_VIVADO=${XILINX_VIVADO:-"/opt/xilinx/Vivado/2024.2"}
|
||||
|
||||
# V80 bitstream refresh require insmod of ami.ko module
|
||||
AMI_PATH=${AMI_PATH:-"/opt/ami_drv/7423535"}
|
||||
|
||||
# Parse user CLI ##############################################################
|
||||
opt_short="hc:l:p:i"
|
||||
opt_long="help,config:,rust-log:pcie-dev:init-qdma"
|
||||
opt_short="hc:l:p:"
|
||||
opt_long="help,config:,rust-log:pcie-dev"
|
||||
OPTS=$(getopt -o "$opt_short" -l "$opt_long" -- "$@")
|
||||
|
||||
while true
|
||||
@@ -40,7 +43,6 @@ do
|
||||
echo " * --config: target configuration [sim, u55c_gf64, v80]"
|
||||
echo " * --rust-log: Specify rust verbosity [Cf. tracing]"
|
||||
echo " * --pcie-dev: target pcie device [Warn: v80 only]"
|
||||
echo " * --init-qdma: init the qdma driver [Warn: v80 only]"
|
||||
return 0
|
||||
;;
|
||||
-c|--config)
|
||||
@@ -72,10 +74,6 @@ do
|
||||
fi
|
||||
shift 2
|
||||
;;
|
||||
-i|--init-qdma)
|
||||
V80_QDMA_INIT=true
|
||||
shift
|
||||
;;
|
||||
"") # End of input reading
|
||||
break ;;
|
||||
*)
|
||||
@@ -95,7 +93,8 @@ if [[ "$HPU_CONFIG" == sim* ]]; then
|
||||
echo "# * Mockup directory: ${HPU_MOCKUP_DIR}"
|
||||
elif [[ "$HPU_CONFIG" == v80* ]]; then
|
||||
echo "# * PCIe id: ${V80_PCIE_DEV} [V80 only]"
|
||||
echo "# * Init Qdma: ${V80_QDMA_INIT} [V80 only]"
|
||||
echo "# * XilinxVivado: ${XILINX_VIVADO} [V80 only]"
|
||||
echo "# * AmiPath: ${AMI_PATH} [V80 only]"
|
||||
fi
|
||||
echo "# * Rust verbosity: ${RUST_LOG}"
|
||||
echo "###############################################################################"
|
||||
@@ -123,24 +122,6 @@ fi
|
||||
# V80 specific init ###########################################################
|
||||
if [[ "$HPU_CONFIG" == v80* ]]; then
|
||||
export V80_PCIE_DEV
|
||||
if [[ "$V80_QDMA_INIT" == true ]]; then
|
||||
while true; do
|
||||
read -p "QDMA_PF init requested by user. This required sudo right, Are you sure to process [Y/n]" user_input
|
||||
if [[ "$user_input" == [Yy] ]]; then
|
||||
echo "Continuing... You could be prompt for sudo password"
|
||||
sudo modprobe -r qdma-pf && sudo modprobe qdma-pf
|
||||
sudo bash -c "echo 100 > /sys/bus/pci/devices/0000\:${V80_PCIE_DEV}\:00.1/qdma/qmax"
|
||||
sudo dma-ctl qdma${V80_PCIE_DEV}001 q add idx 1 mode mm dir h2c
|
||||
sudo dma-ctl qdma${V80_PCIE_DEV}001 q add idx 2 mode mm dir c2h
|
||||
sudo dma-ctl qdma${V80_PCIE_DEV}001 q start idx 1 dir h2c
|
||||
sudo dma-ctl qdma${V80_PCIE_DEV}001 q start idx 2 dir c2h
|
||||
break
|
||||
elif [[ "$user_input" == [Nn] ]]; then
|
||||
echo "Skipped QDMA_PF init"
|
||||
break
|
||||
else
|
||||
echo "Invalid input. Please enter 'Y' or 'n'."
|
||||
fi
|
||||
done
|
||||
fi
|
||||
export XILINX_VIVADO
|
||||
export AMI_PATH
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user