Skip to content

Commit f6c9a5f

Browse files
feat: verification
1 parent 70153a3 commit f6c9a5f

33 files changed

Lines changed: 1596 additions & 263 deletions

File tree

Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[workspace]
2-
members = ["crates/aegis-desc", "crates/aegis-pack", "crates/aegis-sim"]
2+
members = ["crates/aegis-ip", "crates/aegis-pack", "crates/aegis-sim"]
33
resolver = "2"
44

55
[workspace.package]
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
[package]
2-
name = "aegis-desc"
2+
name = "aegis-ip"
33
version.workspace = true
44
edition.workspace = true
5-
description = "Aegis FPGA device descriptor types generated from JSON Schema"
5+
description = "Aegis FPGA device descriptor types and tile config bit layout"
66

77
[dependencies]
88
serde = { workspace = true }
Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
1-
use typify::import_types;
1+
/// Device descriptor types generated from the JSON Schema.
2+
pub mod desc {
3+
use typify::import_types;
4+
import_types!(schema = "../../ip/data/descriptor.schema.json");
5+
}
6+
7+
pub use desc::*;
8+
9+
#[allow(unused_imports)]
10+
pub use tile_bits::TileConfig;
211

3-
import_types!(schema = "../../ip/data/descriptor.schema.json");
12+
pub mod tile_bits;
413

514
#[cfg(test)]
6-
mod tests {
15+
mod desc_tests {
716
use std::num::NonZero;
817

918
use super::*;

crates/aegis-ip/src/tile_bits.rs

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
//! Shared tile config bit layout for Aegis FPGA.
2+
//!
3+
//! This crate is the single source of truth for the tile configuration
4+
//! bitstream layout. Both the packer (aegis-pack) and simulator (aegis-sim)
5+
//! depend on this crate to ensure their bit layouts are identical.
6+
//!
7+
//! Layout for T tracks:
8+
//! [17:0] CLB config (16 LUT init + 1 FF enable + 1 carry mode)
9+
//! [18..18+4*ISW-1] input mux sel0..sel3 (ISW = input_sel_width(T))
10+
//! [18+4*ISW..] per-track output: 4 dirs × T tracks × (1 en + 3 sel)
11+
//!
12+
//! For T=1: 46 bits (backward compatible with original layout)
13+
//! For T=4: 102 bits
14+
15+
// --- Fixed offsets (track-independent) ---
16+
17+
pub const LUT_INIT: usize = 0;
18+
pub const LUT_INIT_WIDTH: usize = 16;
19+
pub const FF_ENABLE: usize = 16;
20+
pub const CARRY_MODE: usize = 17;
21+
pub const INPUT_SEL_BASE: usize = 18;
22+
23+
// --- Output mux select values ---
24+
25+
pub const OUT_MUX_NORTH: u64 = 0;
26+
pub const OUT_MUX_EAST: u64 = 1;
27+
pub const OUT_MUX_SOUTH: u64 = 2;
28+
pub const OUT_MUX_WEST: u64 = 3;
29+
pub const OUT_MUX_CLB: u64 = 4;
30+
31+
pub const OUTPUT_SEL_WIDTH: usize = 3;
32+
33+
// --- Parametric layout functions ---
34+
35+
/// Width of input select field for T tracks.
36+
/// Encodes: N0..N(T-1), E0..E(T-1), S0..S(T-1), W0..W(T-1), CLB_OUT, const0, const1
37+
pub fn input_sel_width(tracks: usize) -> usize {
38+
let n = 4 * tracks + 3;
39+
(usize::BITS - (n - 1).leading_zeros()) as usize
40+
}
41+
42+
/// Bit offset of input sel[idx] for T tracks.
43+
pub fn input_sel_offset(idx: usize, tracks: usize) -> usize {
44+
INPUT_SEL_BASE + idx * input_sel_width(tracks)
45+
}
46+
47+
/// Base offset of the per-track output section.
48+
pub fn output_base(tracks: usize) -> usize {
49+
INPUT_SEL_BASE + 4 * input_sel_width(tracks)
50+
}
51+
52+
/// Enable bit offset for output (dir, track).
53+
pub fn output_en(dir: usize, track: usize, tracks: usize) -> usize {
54+
output_base(tracks) + (dir * tracks + track) * 4
55+
}
56+
57+
/// Select field offset for output (dir, track). 3 bits wide.
58+
pub fn output_sel(dir: usize, track: usize, tracks: usize) -> usize {
59+
output_base(tracks) + (dir * tracks + track) * 4 + 1
60+
}
61+
62+
/// Total tile config width for T tracks.
63+
pub fn tile_config_width(tracks: usize) -> usize {
64+
18 + 4 * input_sel_width(tracks) + 4 * tracks * 4
65+
}
66+
67+
/// Input mux select value for direction + track.
68+
pub fn mux_dir_track(dir: usize, track: usize, tracks: usize) -> u64 {
69+
(dir * tracks + track) as u64
70+
}
71+
72+
/// Input mux select value for CLB output.
73+
pub fn mux_clb_out(tracks: usize) -> u64 {
74+
(4 * tracks) as u64
75+
}
76+
77+
/// Input mux select value for constant 0.
78+
pub fn mux_const0(tracks: usize) -> u64 {
79+
(4 * tracks + 1) as u64
80+
}
81+
82+
/// Input mux select value for constant 1.
83+
pub fn mux_const1(tracks: usize) -> u64 {
84+
(4 * tracks + 2) as u64
85+
}
86+
87+
// --- Bitstream read/write helpers ---
88+
89+
/// Set a single bit in a bitstream buffer.
90+
pub fn set_bit(bits: &mut [u8], offset: usize) {
91+
bits[offset / 8] |= 1 << (offset % 8);
92+
}
93+
94+
/// Clear bits at a given offset and width.
95+
pub fn clear_bits(bits: &mut [u8], offset: usize, width: usize) {
96+
for i in 0..width {
97+
bits[(offset + i) / 8] &= !(1 << ((offset + i) % 8));
98+
}
99+
}
100+
101+
/// Write a value into the bitstream at a given bit offset and width.
102+
pub fn write_bits(bits: &mut [u8], offset: usize, value: u64, width: usize) {
103+
clear_bits(bits, offset, width);
104+
for i in 0..width {
105+
if value & (1 << i) != 0 {
106+
set_bit(bits, offset + i);
107+
}
108+
}
109+
}
110+
111+
/// Read a value from the bitstream at a given bit offset and width.
112+
pub fn read_bits(bits: &[u8], offset: usize, width: usize) -> u64 {
113+
let mut val = 0u64;
114+
for i in 0..width {
115+
let byte_idx = (offset + i) / 8;
116+
let bit_idx = (offset + i) % 8;
117+
if byte_idx < bits.len() && bits[byte_idx] & (1 << bit_idx) != 0 {
118+
val |= 1 << i;
119+
}
120+
}
121+
val
122+
}
123+
124+
/// Read a single bit from the bitstream.
125+
pub fn read_bit(bits: &[u8], offset: usize) -> bool {
126+
let byte_idx = offset / 8;
127+
let bit_idx = offset % 8;
128+
byte_idx < bits.len() && bits[byte_idx] & (1 << bit_idx) != 0
129+
}
130+
131+
// --- Decoded tile configuration ---
132+
133+
/// Decoded tile configuration with per-track output muxes.
134+
#[derive(Clone, Debug, PartialEq)]
135+
pub struct TileConfig {
136+
pub lut_init: u16,
137+
pub ff_enable: bool,
138+
pub carry_mode: bool,
139+
pub sel: [u8; 4],
140+
pub en_out: Vec<Vec<bool>>,
141+
pub sel_out: Vec<Vec<u8>>,
142+
}
143+
144+
impl TileConfig {
145+
pub fn default_for(tracks: usize) -> Self {
146+
Self {
147+
lut_init: 0,
148+
ff_enable: false,
149+
carry_mode: false,
150+
sel: [0; 4],
151+
en_out: vec![vec![false; tracks]; 4],
152+
sel_out: vec![vec![0; tracks]; 4],
153+
}
154+
}
155+
156+
pub fn has_any_config(&self) -> bool {
157+
self.lut_init != 0
158+
|| self.ff_enable
159+
|| self.carry_mode
160+
|| self.en_out.iter().any(|d| d.iter().any(|&e| e))
161+
|| self.sel.iter().any(|&s| s != 0)
162+
}
163+
164+
/// Encode this tile config into a bitstream buffer at the given offset.
165+
pub fn encode(&self, bits: &mut [u8], bit_offset: usize, tracks: usize) {
166+
write_bits(
167+
bits,
168+
bit_offset + LUT_INIT,
169+
self.lut_init as u64,
170+
LUT_INIT_WIDTH,
171+
);
172+
if self.ff_enable {
173+
set_bit(bits, bit_offset + FF_ENABLE);
174+
}
175+
if self.carry_mode {
176+
set_bit(bits, bit_offset + CARRY_MODE);
177+
}
178+
let isw = input_sel_width(tracks);
179+
for i in 0..4 {
180+
write_bits(
181+
bits,
182+
bit_offset + input_sel_offset(i, tracks),
183+
self.sel[i] as u64,
184+
isw,
185+
);
186+
}
187+
for dir in 0..4 {
188+
for t in 0..tracks {
189+
if dir < self.en_out.len() && t < self.en_out[dir].len() && self.en_out[dir][t] {
190+
set_bit(bits, bit_offset + output_en(dir, t, tracks));
191+
}
192+
if dir < self.sel_out.len() && t < self.sel_out[dir].len() {
193+
write_bits(
194+
bits,
195+
bit_offset + output_sel(dir, t, tracks),
196+
self.sel_out[dir][t] as u64,
197+
OUTPUT_SEL_WIDTH,
198+
);
199+
}
200+
}
201+
}
202+
}
203+
204+
/// Decode a tile config from bitstream bits at the given offset.
205+
pub fn decode(bitstream: &[u8], bit_offset: usize, tracks: usize) -> Self {
206+
let isw = input_sel_width(tracks);
207+
208+
let sel = std::array::from_fn(|i| {
209+
read_bits(bitstream, bit_offset + input_sel_offset(i, tracks), isw) as u8
210+
});
211+
212+
let en_out = (0..4)
213+
.map(|d| {
214+
(0..tracks)
215+
.map(|t| read_bit(bitstream, bit_offset + output_en(d, t, tracks)))
216+
.collect()
217+
})
218+
.collect();
219+
220+
let sel_out = (0..4)
221+
.map(|d| {
222+
(0..tracks)
223+
.map(|t| {
224+
read_bits(
225+
bitstream,
226+
bit_offset + output_sel(d, t, tracks),
227+
OUTPUT_SEL_WIDTH,
228+
) as u8
229+
})
230+
.collect()
231+
})
232+
.collect();
233+
234+
TileConfig {
235+
lut_init: read_bits(bitstream, bit_offset + LUT_INIT, LUT_INIT_WIDTH) as u16,
236+
ff_enable: read_bit(bitstream, bit_offset + FF_ENABLE),
237+
carry_mode: read_bit(bitstream, bit_offset + CARRY_MODE),
238+
sel,
239+
en_out,
240+
sel_out,
241+
}
242+
}
243+
}
244+
245+
#[cfg(test)]
246+
#[path = "tile_bits_tests.rs"]
247+
mod tests;

0 commit comments

Comments
 (0)