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
38 changes: 19 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,17 @@ foundry via open PDKs and shuttle services like
### Aegis Luna 1

A compact Aegis device targeting GF180MCU via [wafer.space](https://wafer.space)
(1x1 Full slot, 3.93 x 5.12mm die).

| Resource | Count |
|-------------------|---------------|
| LUT4 | ~760 |
| BRAM (128x8) | 40 tiles |
| DSP18 (18x18 MAC) | 40 tiles |
| I/O pads | 118 |
| SerDes | 1 |
| Clock tiles | 1 (4 outputs) |
| Routing tracks | 1 per edge |
| Resource | Count |
|-------------------|---------------------------|
| Fabric | 23 x 23 tiles (529 total) |
| LUT4 (CLB) | 437 |
| BRAM (128x8) | 46 tiles |
| DSP18 (18x18 MAC) | 46 tiles |
| I/O tiles | 92 (25 bonded bidir) |
| SerDes | 0 |
| Clock tiles | 1 (4 outputs) |
| Routing tracks | 1 per edge |

```bash
nix build .#luna-1 # Generate IP (SV, JSON, chipdb, techmap)
Expand Down Expand Up @@ -84,12 +84,12 @@ The tapeout pipeline synthesizes the FPGA fabric itself to PDK standard cells:
```bash
nix build .#terra-1-tapeout
ls result/
# terra_1_synth.v gate-level netlist (Yosys)
# terra_1_final.def placed & routed layout (OpenROAD)
# terra_1.gds GDS2 for fab submission
# terra_1_layout.png layout render
# timing.rpt timing analysis
# power.rpt power report
# terra_1_synth.v - gate-level netlist (Yosys)
# terra_1_final.def - placed & routed layout (OpenROAD)
# terra_1.gds - GDS2 for fab submission
# terra_1_layout.png - layout render
# timing.rpt - timing analysis
# power.rpt - power report
```

Supports GF180MCU (wafer.space) and Sky130 PDKs.
Expand Down Expand Up @@ -175,20 +175,20 @@ clock tiles -> IO tiles -> SerDes tiles -> fabric tiles (row-major).

## Related Projects

- **[OpenFPGA](https://github.com/lnis-uofu/OpenFPGA)** An open-source FPGA
- **[OpenFPGA](https://github.com/lnis-uofu/OpenFPGA)** - An open-source FPGA
IP generator from the University of Utah. Given an XML architecture
description, it generates synthesizable Verilog for a complete FPGA fabric
along with bitstream tooling and self-testing infrastructure. Silicon-proven
through DARPA's POSH program.

- **[FABulous](https://github.com/FPGA-Research-Manchester/FABulous)** An
- **[FABulous](https://github.com/FPGA-Research-Manchester/FABulous)** - An
open-source embedded FPGA (eFPGA) framework from the University of Manchester.
Generates custom FPGA fabric from CSV-based configuration and integrates Yosys
and nextpnr. Silicon-proven with 12+ tapeouts across nodes from TSMC 180nm
down to 28nm CMOS.

- **[Cologne Chip GateMate](https://colognechip.com/programmable-logic/gatemate/)**
A commercial FPGA on GlobalFoundries 28nm with a fully open-source,
- A commercial FPGA on GlobalFoundries 28nm with a fully open-source,
license-free toolchain built on Yosys, nextpnr, and openFPGALoader. The
silicon itself is proprietary, but it is notable as one of the few commercial
FPGAs to embrace open-source EDA tools end-to-end.
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
Loading
Loading