1use crate::{
9 block_model::{Block, Label, Pin, Route},
10 commit::CommitBuilder,
11 document::{Document, TitleBlockUpdate},
12 geometry::{GridPoint, GridRect, GridSize, PinSlot},
13 id::{Allocator, BlockId, PinId},
14 opcode::{Crud, OpCodes},
15 values::{LabelSide, PinDir, PinSide},
16};
17
18const BLOCK: i32 = 8;
21const PITCH: i32 = 16;
22const MARGIN: i32 = 12;
23const PORT_W: i32 = 4;
26const PORT_H: i32 = 2;
27
28struct GridPins {
32 b0: PinId,
33 b1: PinId,
34 s: PinId,
35 c: PinId,
36}
37
38struct Scale {
42 ids: Allocator,
43 n: usize,
44 sheet: BlockId,
45 grid: Vec<BlockId>,
46 inputs: Vec<PinId>,
47 outputs: Vec<PinId>,
48 pins: Vec<GridPins>,
49}
50
51impl Scale {
52 fn new(n: usize) -> Self {
53 let mut ids = Allocator::default();
54 let sheet = ids.mint();
55 let grid: Vec<BlockId> = (0..n * n).map(|_| ids.mint()).collect();
56 let inputs: Vec<PinId> = (0..n).map(|_| ids.mint()).collect();
57 let outputs: Vec<PinId> = (0..n).map(|_| ids.mint()).collect();
58 let pins: Vec<GridPins> = (0..n * n)
59 .map(|_| GridPins {
60 b0: ids.mint(),
61 b1: ids.mint(),
62 s: ids.mint(),
63 c: ids.mint(),
64 })
65 .collect();
66 Self {
67 ids,
68 n,
69 sheet,
70 grid,
71 inputs,
72 outputs,
73 pins,
74 }
75 }
76
77 fn at(&self, i: usize, j: usize) -> &GridPins {
78 &self.pins[i * self.n + j]
79 }
80}
81
82#[must_use]
88#[expect(clippy::expect_used)]
89pub fn build_scale(n: usize) -> Document {
90 let mut scale = Scale::new(n);
91 let mut builder = CommitBuilder::new(format!("Generated a {n}×{n} scale grid"));
92 let span = (n as i32 - 1) * PITCH + BLOCK;
93
94 builder.push(OpCodes::Block(
95 scale.sheet,
96 Crud::Create(Block {
97 rect: cells(-MARGIN, -MARGIN, span + 2 * MARGIN, span + 2 * MARGIN),
98 title: title(&format!("scale_{n}x{n}")),
99 ..Block::default()
100 }),
101 ));
102 for i in 0..n {
103 for j in 0..n {
104 builder.push(OpCodes::Block(
105 scale.grid[i * n + j],
106 Crud::Create(Block {
107 parent: scale.sheet,
108 rect: cells(j as i32 * PITCH, i as i32 * PITCH, BLOCK, BLOCK),
109 title: title(&format!("block_{i}_{j}")),
110 type_label: Label {
111 name: "Add".to_owned(),
112 ..Label::default()
113 },
114 ..Block::default()
115 }),
116 ));
117 }
118 }
119 builder.push(OpCodes::Document(TitleBlockUpdate::Top(scale.sheet)));
120
121 for (i, &id) in scale.inputs.iter().enumerate() {
124 builder.push(
125 port(id, scale.sheet, &format!("in_{i}"), west(i), PinDir::Input)
126 .in_body(port_body(-MARGIN + 3, i))
127 .op(),
128 );
129 }
130 for (i, &id) in scale.outputs.iter().enumerate() {
131 builder.push(
132 port(
133 id,
134 scale.sheet,
135 &format!("out_{i}"),
136 east(i),
137 PinDir::Output,
138 )
139 .in_body(port_body(span + MARGIN - 3 - PORT_W, i))
140 .op(),
141 );
142 }
143 for index in 0..n * n {
144 let owner = scale.grid[index];
145 let pins = &scale.pins[index];
146 builder.push(port(pins.b0, owner, "b0", west(0), PinDir::Input).op());
147 builder.push(port(pins.b1, owner, "b1", west(1), PinDir::Input).op());
148 builder.push(port(pins.s, owner, "s", east(0), PinDir::Output).op());
149 builder.push(port(pins.c, owner, "c", east(1), PinDir::Output).op());
150 }
151
152 for (from, to, name) in wiring(&scale) {
153 builder.push(OpCodes::Route(
154 scale.ids.mint(),
155 Crud::Create(Route {
156 owner: scale.sheet,
157 name: name.to_owned(),
158 from,
159 to,
160 ..Route::default()
161 }),
162 ));
163 }
164
165 let commit = builder.seal().expect("the grid emits at least one op");
166 Document::default()
167 .try_apply(&commit)
168 .expect("the generated grid folds")
169}
170
171fn wiring(scale: &Scale) -> Vec<(PinId, PinId, &'static str)> {
177 let n = scale.n;
178 let mut wires = Vec::new();
179 for i in 0..n {
182 wires.push((scale.inputs[i], scale.at(i, 0).b0, "in"));
183 wires.push((scale.inputs[i], scale.at((i + 1) % n, 0).b1, "in"));
184 }
185 for i in 0..n {
188 for j in 0..n - 1 {
189 let src = scale.at(i, j).s;
190 wires.push((src, scale.at(i, j + 1).b0, "sum"));
191 wires.push((src, scale.at(i, j + 1).b1, "sum"));
192 }
193 }
194 for i in 0..n {
196 wires.push((scale.at(i, n - 1).s, scale.outputs[i], "out"));
197 }
198 for i in 0..n {
201 for j in 0..n {
202 wires.push((scale.at(i, j).c, scale.at((i + 1) % n, j).b1, "carry"));
203 }
204 }
205 wires
206}
207
208struct PinSpec<'a> {
214 id: PinId,
215 owner: BlockId,
216 name: &'a str,
217 slot: PinSlot,
218 dir: PinDir,
219 body: GridRect,
220}
221
222impl PinSpec<'_> {
223 fn in_body(mut self, body: GridRect) -> Self {
225 self.body = body;
226 self
227 }
228
229 fn op(self) -> OpCodes {
230 OpCodes::Pin(
231 self.id,
232 Crud::Create(Pin {
233 owner: self.owner,
234 name: self.name.to_owned(),
235 type_name: "bit".to_owned(),
236 slot: self.slot,
237 dir: self.dir,
238 rect: self.body,
239 ..Pin::default()
240 }),
241 )
242 }
243}
244
245fn port(id: PinId, owner: BlockId, name: &str, slot: PinSlot, dir: PinDir) -> PinSpec<'_> {
246 PinSpec {
247 id,
248 owner,
249 name,
250 slot,
251 dir,
252 body: GridRect::default(),
253 }
254}
255
256fn port_body(margin_x: i32, row: usize) -> GridRect {
260 cells(margin_x, row as i32 * PITCH, PORT_W, PORT_H)
261}
262
263fn west(offset: usize) -> PinSlot {
264 PinSlot {
265 side: PinSide::West,
266 offset: offset as u32,
267 }
268}
269
270fn east(offset: usize) -> PinSlot {
271 PinSlot {
272 side: PinSide::East,
273 offset: offset as u32,
274 }
275}
276
277fn title(name: &str) -> Label {
278 Label {
279 name: name.to_owned(),
280 side: LabelSide::Bottom,
281 ..Label::default()
282 }
283}
284
285fn cells(x: i32, y: i32, w: i32, h: i32) -> GridRect {
286 GridRect {
287 top_left: GridPoint { x, y },
288 size: GridSize {
289 w: w.unsigned_abs(),
290 h: h.unsigned_abs(),
291 },
292 }
293}