Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions crates/aegis-ip/src/tile_bits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//! [18..18+4*ISW-1] input mux sel0..sel3 (ISW = input_sel_width(T))
//! [18+4*ISW..] per-track output: 4 dirs × T tracks × (1 en + 3 sel)
//!
//! For T=1: 46 bits (backward compatible with original layout)
//! For T=1: 50 bits
//! For T=4: 102 bits

// --- Fixed offsets (track-independent) ---
Expand All @@ -33,9 +33,10 @@ pub const OUTPUT_SEL_WIDTH: usize = 3;
// --- Parametric layout functions ---

/// Width of input select field for T tracks.
/// Encodes: N0..N(T-1), E0..E(T-1), S0..S(T-1), W0..W(T-1), CLB_OUT, const0, const1
/// Encodes: N0..N(T-1), E0..E(T-1), S0..S(T-1), W0..W(T-1),
/// CLB_OUT, const0, const1, NB_N, NB_E, NB_S, NB_W
pub fn input_sel_width(tracks: usize) -> usize {
let n = 4 * tracks + 3;
let n = 4 * tracks + 7;
(usize::BITS - (n - 1).leading_zeros()) as usize
}

Expand Down Expand Up @@ -84,6 +85,11 @@ pub fn mux_const1(tracks: usize) -> u64 {
(4 * tracks + 2) as u64
}

/// Input mux select value for neighbor CLB output (direction 0=N, 1=E, 2=S, 3=W).
pub fn mux_neighbor(dir: usize, tracks: usize) -> u64 {
(4 * tracks + 3 + dir) as u64
}

// --- Bitstream read/write helpers ---

/// Set a single bit in a bitstream buffer.
Expand Down
49 changes: 26 additions & 23 deletions crates/aegis-ip/src/tile_bits_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ use super::*;
// === Layout formula tests ===

#[test]
fn t1_backward_compatible_width() {
assert_eq!(tile_config_width(1), 46);
fn t1_width() {
// 18 + 4*4 + 4*1*4 = 18 + 16 + 16 = 50
assert_eq!(tile_config_width(1), 50);
}

#[test]
Expand All @@ -21,36 +22,34 @@ fn t4_width() {

#[test]
fn input_sel_width_values() {
assert_eq!(input_sel_width(1), 3); // 7 values -> 3 bits
assert_eq!(input_sel_width(2), 4); // 11 values -> 4 bits
assert_eq!(input_sel_width(4), 5); // 19 values -> 5 bits
assert_eq!(input_sel_width(1), 4); // 11 values -> 4 bits
assert_eq!(input_sel_width(2), 4); // 15 values -> 4 bits
assert_eq!(input_sel_width(4), 5); // 23 values -> 5 bits
}

#[test]
fn input_sel_offsets_t1() {
assert_eq!(input_sel_offset(0, 1), 18);
assert_eq!(input_sel_offset(1, 1), 21);
assert_eq!(input_sel_offset(2, 1), 24);
assert_eq!(input_sel_offset(3, 1), 27);
assert_eq!(input_sel_offset(1, 1), 22);
assert_eq!(input_sel_offset(2, 1), 26);
assert_eq!(input_sel_offset(3, 1), 30);
}

#[test]
fn output_base_t1() {
// 18 + 4*3 = 30
assert_eq!(output_base(1), 30);
// 18 + 4*4 = 34
assert_eq!(output_base(1), 34);
}

#[test]
fn output_offsets_t1_match_original_layout() {
// Original layout: EN_NORTH=30, EN_EAST=31, EN_SOUTH=32, EN_WEST=33
// SEL_NORTH=34, SEL_EAST=37, SEL_SOUTH=40, SEL_WEST=43
// New layout: output_en(dir, 0, 1) = 30 + dir*4, output_sel(dir, 0, 1) = 30 + dir*4 + 1
assert_eq!(output_en(0, 0, 1), 30); // EN_NORTH
assert_eq!(output_sel(0, 0, 1), 31); // SEL_NORTH at 31 (was 34)
// Note: the new layout packs (en, sel[2:0]) as 4 contiguous bits per track,
// which differs from the original layout where enables were grouped together.
// For T=1 the total width is still 46, but the bit positions within the
// output section differ. The Dart tile_config.dart uses the new layout.
fn output_offsets_t1() {
// output_base(1) = 34
assert_eq!(output_en(0, 0, 1), 34); // EN_NORTH
assert_eq!(output_sel(0, 0, 1), 35); // SEL_NORTH
assert_eq!(output_en(1, 0, 1), 38); // EN_EAST
assert_eq!(output_en(2, 0, 1), 42); // EN_SOUTH
assert_eq!(output_en(3, 0, 1), 46); // EN_WEST
// Last bit: 46 + 3 = 49, total width = 50
}

#[test]
Expand All @@ -73,14 +72,18 @@ fn output_offsets_t4() {
// === Mux select value tests ===

#[test]
fn mux_values_t1_backward_compatible() {
fn mux_values_t1() {
assert_eq!(mux_dir_track(0, 0, 1), 0); // N0
assert_eq!(mux_dir_track(1, 0, 1), 1); // E0
assert_eq!(mux_dir_track(2, 0, 1), 2); // S0
assert_eq!(mux_dir_track(3, 0, 1), 3); // W0
assert_eq!(mux_clb_out(1), 4);
assert_eq!(mux_const0(1), 5);
assert_eq!(mux_const1(1), 6);
assert_eq!(mux_neighbor(0, 1), 7); // NB_N
assert_eq!(mux_neighbor(1, 1), 8); // NB_E
assert_eq!(mux_neighbor(2, 1), 9); // NB_S
assert_eq!(mux_neighbor(3, 1), 10); // NB_W
}

#[test]
Expand All @@ -100,7 +103,7 @@ fn mux_values_t4() {
fn all_mux_values_fit_in_input_sel_width() {
for tracks in [1, 2, 4, 8] {
let isw = input_sel_width(tracks);
let max_val = mux_const1(tracks);
let max_val = mux_neighbor(3, tracks);
assert!(
max_val < (1 << isw),
"max mux value {} doesn't fit in {} bits for T={}",
Expand Down Expand Up @@ -395,7 +398,7 @@ fn max_lut_init_roundtrips() {
#[test]
fn max_sel_value_roundtrips() {
for tracks in [1, 2, 4] {
let max_sel = mux_const1(tracks) as u8;
let max_sel = mux_neighbor(3, tracks) as u8;
let mut cfg = TileConfig::default_for(tracks);
cfg.sel = [max_sel; 4];
let mut bits = vec![0u8; (tile_config_width(tracks) + 7) / 8];
Expand Down
66 changes: 50 additions & 16 deletions crates/aegis-pack/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,39 @@ fn pack_routing_pip(
}
}

// Neighbor direct connections: adjacent CLB output -> this tile's CLB input
if src_gx != dst_gx || src_gy != dst_gy {
if let Some(rest) = dst_wire.strip_prefix("CLB_I") {
if let Ok(idx) = rest.parse::<usize>() {
if idx < 4 && (src_wire == "CLB_O" || src_wire == "CLB_Q") {
let nb_dir = if dy == 1 {
0
}
// src is north
else if dx == -1 {
1
}
// src is east
else if dy == -1 {
2
}
// src is south
else {
3
}; // src is west
if let Some(&(tile_offset, config_width)) = tile_offsets.get(&(dst_x, dst_y)) {
let min_width = tile_bits::tile_config_width(tracks);
if config_width >= min_width {
let base = fabric_base + tile_offset;
let isw = tile_bits::input_sel_width(tracks);
let sel_val = tile_bits::mux_neighbor(nb_dir, tracks);
let sel_offset = base + tile_bits::input_sel_offset(idx, tracks);
write_bits(bits, sel_offset, sel_val, isw);
}
}
}
}
}
return;
}

Expand Down Expand Up @@ -428,9 +460,11 @@ fn parse_track_wire(wire: &str) -> Option<(usize, usize)> {
Some((dir, track))
}

/// Extract the track number from a wire name (e.g., "S1" -> 1, "N0" -> 0).
/// Extract the track number from a wire name (e.g., "S1" -> 1, "N0" -> 0, "OUT_N0" -> 0).
fn parse_track(wire: &str) -> Option<usize> {
parse_track_wire(wire).map(|(_, t)| t)
parse_track_wire(wire)
.or_else(|| parse_output_mux_wire(wire))
.map(|(_, t)| t)
}

/// Parse a per-track output mux wire like "OUT_N0", "OUT_E3".
Expand Down Expand Up @@ -529,7 +563,7 @@ mod tests {
"device": "test",
"fabric": {
"width": 2, "height": 2, "tracks": 1,
"tile_config_width": 46,
"tile_config_width": 50,
"bram": { "column_interval": 0, "columns": [],
"data_width": null, "addr_width": null,
"depth": null, "tile_config_width": 8 },
Expand All @@ -543,19 +577,19 @@ mod tests {
"clock": { "tile_count": 0, "tile_config_width": 49,
"outputs_per_tile": 4, "total_outputs": 0 },
"config": {
"total_bits": 248,
"total_bits": 264,
"chain_order": [
{ "section": "io_tiles", "count": 8,
"bits_per_tile": 8, "total_bits": 64 },
{ "section": "fabric_tiles", "count": 4,
"total_bits": 184 }
"total_bits": 200 }
]
},
"tiles": [
{ "x": 0, "y": 0, "type": "lut", "config_width": 46, "config_offset": 0 },
{ "x": 1, "y": 0, "type": "lut", "config_width": 46, "config_offset": 46 },
{ "x": 0, "y": 1, "type": "lut", "config_width": 46, "config_offset": 92 },
{ "x": 1, "y": 1, "type": "lut", "config_width": 46, "config_offset": 138 }
{ "x": 0, "y": 0, "type": "lut", "config_width": 50, "config_offset": 0 },
{ "x": 1, "y": 0, "type": "lut", "config_width": 50, "config_offset": 50 },
{ "x": 0, "y": 1, "type": "lut", "config_width": 50, "config_offset": 100 },
{ "x": 1, "y": 1, "type": "lut", "config_width": 50, "config_offset": 150 }
]
}"#,
)
Expand Down Expand Up @@ -656,7 +690,7 @@ mod tests {
let pnr = test_pnr_with_lut(1, 0, "16'h1234");
let bits = pack(&desc, &pnr);

let init = read_bits(&bits, 64 + 46, 16); // tile (1,0) offset=46
let init = read_bits(&bits, 64 + 50, 16); // tile (1,0) offset=50
assert_eq!(init, 0x1234);
}

Expand Down Expand Up @@ -713,8 +747,8 @@ mod tests {
);
let bits = pack(&desc, &PnrOutput { modules });

// tile (1,1) offset=138
assert_ne!(read_bits(&bits, 64 + 138 + tile_bits::CARRY_MODE, 1), 0);
// tile (1,1) offset=150
assert_ne!(read_bits(&bits, 64 + 150 + tile_bits::CARRY_MODE, 1), 0);
}

#[test]
Expand All @@ -737,7 +771,7 @@ mod tests {
let bits = pack(&desc, &pnr);

let isw = tile_bits::input_sel_width(tracks);
let sel = read_bits(&bits, 64 + 46 + tile_bits::input_sel_offset(2, tracks), isw);
let sel = read_bits(&bits, 64 + 50 + tile_bits::input_sel_offset(2, tracks), isw);
assert_eq!(sel, tile_bits::mux_dir_track(1, 0, tracks)); // E0
}

Expand Down Expand Up @@ -779,14 +813,14 @@ mod tests {
let pnr = test_pnr_with_routing(&["X2/Y2/OUT_W0/X2/Y2/CLB_Q"]);
let bits = pack(&desc, &pnr);

// tile (1,1) offset=138
// tile (1,1) offset=150
assert_ne!(
read_bits(&bits, 64 + 138 + tile_bits::output_en(3, 0, tracks), 1),
read_bits(&bits, 64 + 150 + tile_bits::output_en(3, 0, tracks), 1),
0
);
let sel = read_bits(
&bits,
64 + 138 + tile_bits::output_sel(3, 0, tracks),
64 + 150 + tile_bits::output_sel(3, 0, tracks),
tile_bits::OUTPUT_SEL_WIDTH,
);
assert_eq!(sel, tile_bits::OUT_MUX_CLB);
Expand Down
28 changes: 22 additions & 6 deletions crates/aegis-sim/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,19 +198,19 @@ impl Simulator {
false
};

// CLB output matches Dart: mux(carryMode, sum, mux(useFF, ffQ, lutOut))
let clb_out = if cfg.carry_mode {
lut_out ^ carry_in
} else if cfg.ff_enable {
self.state[x][y].ff_q
} else {
lut_out
};

self.next_state[x][y].lut_out = clb_out;
self.next_state[x][y].carry_out = carry_out;
self.next_state[x][y].ff_q = if cfg.ff_enable {
clb_out
} else {
self.state[x][y].ff_q
};
// FF always captures LUT output (Dart: Sequential(clk, [ffQ < lutOut]))
self.next_state[x][y].ff_q = lut_out;

// Per-track output routing
for dir in 0..4usize {
Expand Down Expand Up @@ -287,13 +287,15 @@ impl Simulator {
}

/// Input mux: decode select value to get input signal.
/// Encoding: dir*T + track for directional, 4*T for CLB_OUT, 4*T+1 for const0, 4*T+2 for const1.
/// Encoding: dir*T + track for directional, 4*T for CLB_OUT,
/// 4*T+1 for const0, 4*T+2 for const1, 4*T+3..4*T+6 for neighbor N/E/S/W.
fn select_input(&self, x: usize, y: usize, sel: u8) -> bool {
let sel = sel as usize;
let t = self.tracks;
let clb_out_val = 4 * t;
let const0_val = 4 * t + 1;
let const1_val = 4 * t + 2;
let nb_base = 4 * t + 3;

if sel < clb_out_val {
let dir = sel / t;
Expand All @@ -305,6 +307,20 @@ impl Simulator {
false
} else if sel == const1_val {
true
} else if sel >= nb_base && sel < nb_base + 4 {
// Neighbor CLB output: N=0, E=1, S=2, W=3
let nb_dir = sel - nb_base;
let (nx, ny) = match nb_dir {
0 => (x, y.wrapping_sub(1)), // north
1 => (x + 1, y), // east
2 => (x, y + 1), // south
_ => (x.wrapping_sub(1), y), // west
};
if nx < self.gw && ny < self.gh {
self.state[nx][ny].lut_out
} else {
false
}
} else {
false
}
Expand Down
41 changes: 41 additions & 0 deletions crates/aegis-sim/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ struct Args {
/// Clock pad by edge and position (e.g., w0)
#[arg(long)]
clock_pin: Option<String>,

/// Set a pin high for a cycle range: "w1:0-9" sets west pad 1 high
/// during cycles 0 through 9. Multiple allowed.
#[arg(long, value_delimiter = ',')]
set_pin: Vec<String>,
}

fn main() {
Expand Down Expand Up @@ -122,11 +127,47 @@ fn main() {
eprintln!("Clock pad: {cp}");
}

// Parse --set-pin entries: "w1:0-9" -> (pad_idx, start_cycle, end_cycle)
let mut stimuli: Vec<(usize, u64, u64)> = Vec::new();
for spec in &args.set_pin {
let parts: Vec<&str> = spec.split(':').collect();
if parts.len() != 2 {
eprintln!("Invalid --set-pin format '{spec}', expected 'pin:start-end'");
continue;
}
let pin = parts[0];
let (edge, pos) = pin.split_at(1);
let p: usize = pos.parse().expect("Invalid pin position");
let pad_idx = match edge {
"n" | "N" => p,
"e" | "E" => fw + p,
"s" | "S" => fw + fh + p,
"w" | "W" => 2 * fw + fh + p,
_ => {
eprintln!("Unknown edge '{edge}' in set-pin '{spec}'");
continue;
}
};
let range: Vec<&str> = parts[1].split('-').collect();
let start: u64 = range[0].parse().expect("Invalid start cycle");
let end: u64 = if range.len() > 1 {
range[1].parse().expect("Invalid end cycle")
} else {
args.cycles
};
eprintln!("Set pin {pin} (pad {pad_idx}) high for cycles {start}-{end}");
stimuli.push((pad_idx, start, end));
}

for cycle in 0..args.cycles {
// Toggle clock pad each cycle
if let Some(cp) = clock_pad {
sim.set_io(cp, cycle % 2 == 0);
}
// Apply stimuli
for &(pad, start, end) in &stimuli {
sim.set_io(pad, cycle >= start && cycle <= end);
}
sim.step();

if let Some(ref mut w) = vcd {
Expand Down
Loading
Loading