1use std::collections::{BTreeMap, HashMap};
21use std::fmt::Write as _;
22
23use crate::model::quant::GgmlType;
24use crate::model::spec::{Mixer, ModelSpec};
25use crate::spatial::decode_engine::machine::{encode, BlockState, Fold, Job, Layout, LogFields, Row, StoreLayout, LANES, LINE};
26use crate::spatial::decode_engine::model::{CacheFormat, StateFormat};
27use crate::spatial::decode_engine::numerics::{ActScaling, FpUnit, Words};
28use crate::spatial::decode_engine::vector::{Bin, Func, Op};
29use crate::spatial::serving::StateFlush;
30use super::ram::STORE_READ_LATENCY;
31
32use crate::codegen_sva::fp_hard::{self, hard_verilog, hard_verilog_for, plan, plan_for, Block, HardError};
33use crate::codegen_sva::fp_hypotheses::Semantics;
34use crate::codegen_sva::fp_oracle::{hard_model_verilog, Depths};
35
36use super::column::ColumnSet;
37use super::fp32::{FADD_LATENCY, FMUL_LATENCY};
38use super::super::image::ImageLayout;
39
40#[derive(Clone, Debug, PartialEq)]
42pub struct Tables {
43 pub jobs: Vec<u64>,
45 pub rows: Vec<u128>,
47 pub slots: Vec<u64>,
49 pub tensors: Vec<u128>,
51 pub ropes: Vec<u64>,
53 pub blocks: Vec<[u32; 16]>,
60 pub state_f16: bool,
63 pub cache_int8: bool,
64 pub two_words: bool,
65 pub per_block: bool,
66}
67
68pub fn logged(flush: StateFlush) -> usize {
72 match flush {
73 StateFlush::EveryPass => 1,
74 StateFlush::Every { tokens } => tokens as usize,
75 StateFlush::Trailing { .. } => panic!("a trailing flush, which only the recurrence's head lanes replay"),
76 }
77}
78
79pub fn behind(flush: Option<StateFlush>) -> String {
82 match flush {
83 Some(StateFlush::Trailing { tokens }) => format!("+behind{tokens}"),
84 _ => String::new(),
85 }
86}
87
88pub fn recurrence_log(flush: StateFlush) -> (usize, Option<usize>) {
91 match flush {
92 StateFlush::Trailing { tokens } => (2 * tokens as usize, Some(tokens as usize)),
93 f => (logged(f), None),
94 }
95}
96
97pub fn tables(layout: &Layout, store: &StoreLayout, jobs: &[Job], rows: &[Row]) -> Tables {
99 tables_at(layout, store, jobs, rows, &|at| Some(at))
100}
101
102pub fn tables_at(layout: &Layout, store: &StoreLayout, jobs: &[Job], rows: &[Row], place: &dyn Fn(u64) -> Option<u64>) -> Tables {
107 let placed = |at: u64| place(at).expect("a place for every region and rotary table");
108 let line = |bytes: u64| -> u32 {
109 assert_eq!(bytes % LINE, 0, "a place off a line");
110 u32::try_from(bytes / LINE).expect("a line within 32 bits")
111 };
112 Tables {
113 jobs: encode(jobs),
114 rows: rows
115 .iter()
116 .map(|r| line(placed(layout.region_of(r.sequence))) as u128 | (r.position as u128) << 32 | (r.ring as u128) << 64)
117 .collect(),
118 slots: store.slots.iter().map(|&(base, beats)| base as u64 | (beats as u64) << 32).collect(),
119 tensors: layout
120 .tensors
121 .iter()
122 .map(|t| {
123 let dims = |i: usize| t.dims.get(i).copied().unwrap_or(1) as u128;
124 line(place(t.at).unwrap_or(0)) as u128 | (t.ty.id() as u128) << 32 | dims(0) << 40 | dims(1) << 64
125 })
126 .collect(),
127 ropes: layout.rotary.iter().map(|(rope, at)| line(placed(*at)) as u64 | ((rope.dims / 2) as u64) << 32).collect(),
128 blocks: layout
129 .blocks
130 .iter()
131 .map(|b| match *b {
132 BlockState::Delta { ring, channels, taps, memory, log, entry, k_heads, v_heads, width } => {
133 let f = LogFields::of(k_heads, v_heads, width);
134 let tokens = match layout.numerics.flush {
135 StateFlush::EveryPass => 0,
136 StateFlush::Trailing { tokens } => tokens,
137 flush => logged(flush) as u32,
138 };
139 [
140 ring as u32,
141 channels as u32,
142 taps.saturating_sub(1) as u32,
143 memory as u32,
144 log.unwrap_or(0) as u32,
145 entry as u32,
146 k_heads as u32,
147 v_heads as u32,
148 width as u32,
149 f.values as u32,
150 f.log_decays as u32,
151 f.gates as u32,
152 tokens,
153 ((1.0 / (width as f64).sqrt()) as f32).to_bits(),
154 0,
155 0,
156 ]
157 }
158 BlockState::Attention { keys, values, key_scales, value_scales, kv_heads, width, value_width } => [
159 keys as u32,
160 values as u32,
161 key_scales.unwrap_or(0) as u32,
162 value_scales.unwrap_or(0) as u32,
163 kv_heads as u32,
164 width as u32,
165 value_width as u32,
166 0,
167 0,
168 0,
169 0,
170 0,
171 0,
172 0,
173 0,
174 1,
175 ],
176 })
177 .collect(),
178 state_f16: layout.numerics.state == StateFormat::F16,
179 cache_int8: layout.numerics.cache != CacheFormat::F16,
180 two_words: layout.numerics.activations == Words::Two,
181 per_block: layout.numerics.scaling == ActScaling::PerBlock,
182 }
183}
184
185#[derive(Clone, Debug, PartialEq)]
190pub struct EngineParams {
191 pub rows: usize,
192 pub vector_rows: usize,
193 pub side_rows: usize,
197 pub pair_rows: usize,
200 pub store_beats: usize,
201 pub job_words: usize,
202 pub tensors: usize,
203 pub layers: usize,
204 pub ropes: usize,
205 pub formats: Vec<(GgmlType, usize)>,
206 pub delta: Option<(usize, StateFlush)>,
209 pub attend: Option<(usize, usize, usize, bool)>,
212 pub function_units: usize,
214 pub folds: bool,
217 pub streaming: Option<Streaming>,
220}
221
222#[derive(Clone, Debug, PartialEq)]
227pub struct Streaming {
228 pub set: ColumnSet,
229 pub layout: ImageLayout,
230 pub recurrence: bool,
235 pub recurrence_width: usize,
239 pub recurrence_lanes: usize,
244 pub log_lines: usize,
248 pub recurrence_flushes: usize,
252 pub recurrence_credit: usize,
256 pub vectors: bool,
260 pub attention: bool,
264 pub hard_float: Option<HardFloat>,
267 pub float_rows: usize,
270 pub vector_passes: usize,
275 pub vector_depth: usize,
279 pub contexts: usize,
283 pub folds: usize,
287 pub delta_norm: bool,
292 pub conv_lanes: usize,
296 pub conv_beats: usize,
297}
298
299#[derive(Clone, Copy, Debug, PartialEq, Eq)]
307pub struct HardFloat {
308 pub product: Option<Block>,
309 pub sum: Block,
310 pub fp_unit: FpUnit,
311}
312
313impl HardFloat {
314 pub fn product_target(&self) -> Semantics {
316 match self.fp_unit {
317 FpUnit::Soft => Semantics::ENGINE,
318 FpUnit::Hard => FpUnit::CARD,
319 }
320 }
321}
322
323impl Streaming {
324 pub fn new(set: ColumnSet, layout: ImageLayout) -> Streaming {
326 assert!(layout.stripe.is_some_and(|n| n > 0), "a streaming unit's matrices lie in stripes");
327 assert!(
328 layout.octets == set.octets && layout.slot == set.slot as u64,
329 "a streaming unit's set and its image lay blocks alike: octets {} and {}, slots {} and {}",
330 set.octets,
331 layout.octets,
332 set.slot,
333 layout.slot
334 );
335 Streaming {
336 set,
337 layout,
338 recurrence: false,
339 recurrence_width: 1,
340 recurrence_lanes: 1,
341 log_lines: 0,
342 recurrence_flushes: 1,
343 recurrence_credit: super::gdn::LANE_CREDIT,
344 vectors: false,
345 attention: false,
346 hard_float: None,
347 float_rows: super::vector_side::FM_ROWS,
348 vector_passes: 1,
349 vector_depth: 1,
350 contexts: 1,
351 folds: 0,
352 delta_norm: false,
353 conv_lanes: 0,
354 conv_beats: 0,
355 }
356 }
357
358 pub fn with_conv_path(self, lanes: usize, beats: usize) -> Streaming {
364 assert!(self.vectors, "a convolution's path is the vector unit's side's");
365 assert!([1, 2, 4, 8, 16].contains(&lanes), "a conv path of 1, 2, 4, 8 or 16 lanes, not {lanes}");
366 assert!(beats >= 1, "a conv path's kernel holds a beat a tap at least");
367 Streaming { conv_lanes: lanes, conv_beats: beats, ..self }
368 }
369
370 pub fn conv_beats<'a>(specs: impl IntoIterator<Item = &'a ModelSpec>) -> usize {
373 specs
374 .into_iter()
375 .flat_map(|spec| spec.layers.iter())
376 .filter_map(|layer| match &layer.mixer {
377 Mixer::GatedDelta(g) => Some((2 * g.k_heads + g.v_heads) * g.head_dim),
378 _ => None,
379 })
380 .max()
381 .map_or(1, |channels| channels.div_ceil(16))
382 }
383
384 pub fn with_recurrence(self) -> Streaming {
386 Streaming { recurrence: true, ..self }
387 }
388
389 pub fn with_recurrence_width(self, width: usize) -> Streaming {
391 assert!(self.recurrence, "a recurrence unit's width needs the recurrence unit");
392 assert!(width == 1 || width == 2, "the recurrence unit takes half a binary16 line a clock or a whole one, not {width} sixteens");
393 Streaming { recurrence_width: width, ..self }
394 }
395
396 pub fn with_recurrence_lanes(self, lanes: usize, log_lines: usize) -> Streaming {
399 assert!(self.recurrence, "head lanes are the recurrence unit's");
400 assert!((1..=4).contains(&lanes), "the recurrence unit runs on 1 to 4 head lanes, not {lanes}");
401 assert!(lanes == 1 || log_lines >= 1, "head lanes read the log from copies of at least a line");
402 Streaming { recurrence_lanes: lanes, log_lines: if lanes == 1 { 0 } else { log_lines }, ..self }
403 }
404
405 pub fn with_recurrence_flushes(self, flushes: usize) -> Streaming {
407 assert!(self.recurrence_lanes > 1, "flush units are a head lane's");
408 assert!(self.recurrence_width == 2, "a head lane's flush units take a binary16 line a clock");
409 assert!([1, 2, 4, 8].contains(&flushes), "a head lane's flush over 1, 2, 4 or 8 units, not {flushes}");
410 Streaming { recurrence_flushes: flushes, ..self }
411 }
412
413 pub fn with_recurrence_credit(self, lines: usize) -> Streaming {
416 use super::gdn::{LANE_CREDIT, LANE_CREDIT_MAX};
417 assert!(self.recurrence_lanes > 1, "a read credit is the head lanes'");
418 assert!(
419 (LANE_CREDIT..=LANE_CREDIT_MAX).contains(&lines),
420 "a head lane asks {LANE_CREDIT} to {LANE_CREDIT_MAX} lines ahead, not {lines}"
421 );
422 Streaming { recurrence_credit: lines, ..self }
423 }
424
425 pub fn with_vectors(self) -> Streaming {
427 assert!(self.recurrence, "the vector unit's side runs on the recurrence unit's port");
428 assert_eq!(self.layout.line_bytes, 64, "the vector unit's side reads and writes the channels' 64-byte lines");
429 Streaming { vectors: true, ..self }
430 }
431
432 pub fn with_float_rows(self, rows: usize) -> Streaming {
434 assert!(self.vectors, "a float matrix's rows are the vector unit's side's");
435 assert!(rows >= 1, "a float matrix's group of no rows");
436 Streaming { float_rows: rows, ..self }
437 }
438
439 pub fn with_vector_passes(self, passes: usize) -> Streaming {
442 assert!(self.vectors, "a group of passes is the vector unit's side's");
443 assert!(
444 (1..=VECTOR_PASSES_MAX).contains(&passes),
445 "the vector unit runs 1 to {VECTOR_PASSES_MAX} passes at once, not {passes}"
446 );
447 Streaming { vector_passes: passes, ..self }
448 }
449
450 pub fn with_vector_depth(self, depth: usize) -> Streaming {
454 assert!(self.vectors, "a register's depth is the vector unit's side's");
455 assert!(
456 (1..=VECTOR_PASSES_MAX).contains(&depth),
457 "the vector unit's side holds 1 to {VECTOR_PASSES_MAX} passes of its deepest job, not {depth}"
458 );
459 Streaming { vector_depth: depth, ..self }
460 }
461
462 pub fn with_attention(self) -> Streaming {
464 assert!(self.vectors, "the attention side runs beside the vector unit's side, on the recurrence unit's port");
465 Streaming { attention: true, ..self }
466 }
467
468 pub fn with_contexts(self, contexts: usize) -> Streaming {
472 assert!(self.attention, "contexts share the fast side's units, so every unit must be on it: the attention side too");
473 assert!(contexts == 1 || contexts == 2, "an engine of 1 or 2 contexts, not {contexts}");
474 Streaming { contexts, ..self }
475 }
476
477 pub fn with_hard_float(self, hard: HardFloat) -> Result<Streaming, HardError> {
481 assert!(hard.fp_unit == FpUnit::Soft || hard.product.is_some(), "a build of the card's products builds them on its block");
482 if let Some(product) = &hard.product {
483 plan_for(fp_hard::Op::Multiply, product, FMUL_LATENCY, &hard.product_target())?;
484 }
485 plan(fp_hard::Op::Add, &hard.sum, FADD_LATENCY)?;
486 Ok(Streaming { hard_float: Some(hard), ..self })
487 }
488
489 pub fn with_folds(self, lanes: usize) -> Streaming {
492 assert!([1, 2, 4, 8, 16].contains(&lanes), "a fold over 1, 2, 4, 8 or 16 lanes, not {lanes}");
493 Streaming { folds: lanes, ..self }
494 }
495
496 pub fn with_delta_norm(self) -> Streaming {
499 assert!(self.recurrence_lanes > 1, "a recurrence job's gated norm is made on its head lanes");
500 Streaming { delta_norm: true, ..self }
501 }
502
503 pub fn signature(&self) -> String {
507 format!(
508 "{}/{}{}{}",
509 self.set.signature(),
510 self.pairs(),
511 if self.folds > 0 { "+folds" } else { "" },
512 if self.delta_norm { "+norm" } else { "" }
513 )
514 }
515
516 pub fn pairs(&self) -> u64 {
518 self.layout.stripe.expect("a stripe")
519 }
520}
521
522pub const FUNCTION_UNITS: usize = 4;
525
526pub const VECTOR_PASSES_MAX: usize = 32;
529
530pub const CONV_PATH_VREGS: usize = 8;
533
534impl EngineParams {
535 pub fn streaming_signature(&self) -> String {
539 self.streaming.as_ref().map_or(String::new(), |s| format!("{}{}", s.signature(), behind(self.delta.map(|(_, f)| f))))
540 }
541
542 pub fn of(layout: &Layout, store: &StoreLayout, jobs: &[Job]) -> EngineParams {
544 let mut formats: Vec<(GgmlType, usize)> = Vec::new();
545 let (mut vector_rows, mut side_rows, mut pair_rows) = (1, 1, 1);
546 for job in jobs {
547 match job {
548 Job::Matvec { tensor, .. } => {
549 let t = &layout.tensors[*tensor];
550 let blocks = t.dims[0] as usize / t.ty.block_len();
551 match formats.iter_mut().find(|(ty, _)| *ty == t.ty) {
552 Some(f) => f.1 = f.1.max(blocks),
553 None => formats.push((t.ty, blocks)),
554 }
555 }
556 Job::Vector(v) => {
557 let rows = v.loads.iter().map(|l| l.len.div_ceil(LANES)).fold(1, usize::max);
558 vector_rows = vector_rows.max(rows);
559 if !v.chunked() {
560 side_rows = side_rows.max(rows);
561 }
562 if v.program.iter().any(|op| matches!(op, Op::Hadamard { .. } | Op::Rotate { .. })) {
563 pair_rows = pair_rows.max(rows);
564 }
565 }
566 _ => {}
567 }
568 }
569 formats.sort_by_key(|(ty, _)| ty.id());
570 EngineParams {
571 rows: store.rows,
572 vector_rows,
573 side_rows,
574 pair_rows,
575 store_beats: store.beats as usize,
576 job_words: encode(jobs).len().max(1),
577 tensors: layout.tensors.len(),
578 layers: layout.blocks.len(),
579 ropes: layout.rotary.len(),
580 formats,
581 delta: layout.blocks.iter().find_map(|b| match b {
582 BlockState::Delta { width, .. } => Some((*width, layout.numerics.flush)),
583 _ => None,
584 }),
585 attend: layout.blocks.iter().find_map(|b| match b {
586 BlockState::Attention { width, value_width, .. } => {
587 Some((*width, *value_width, layout.ctx, layout.numerics.attention == crate::spatial::serving::AttentionMath::Array))
588 }
589 _ => None,
590 }),
591 function_units: FUNCTION_UNITS,
592 folds: jobs.iter().any(|j| matches!(j, Job::Matvec { fold, .. } if *fold != Fold::Put)),
593 streaming: None,
594 }
595 }
596
597 pub fn union(all: &[EngineParams]) -> Result<EngineParams, String> {
603 let (first, rest) = all.split_first().ok_or("no engines")?;
604 let mut u = first.clone();
605 for p in rest {
606 if p.function_units != u.function_units {
607 return Err(format!("{} and {} function units", u.function_units, p.function_units));
608 }
609 if p.streaming != u.streaming {
610 return Err(format!("streaming units {:?} and {:?}", u.streaming, p.streaming));
611 }
612 u.delta = match (u.delta, p.delta) {
613 (Some(a), Some(b)) if a != b => return Err(format!("recurrence units {a:?} and {b:?}")),
614 (a, b) => a.or(b),
615 };
616 u.attend = match (u.attend, p.attend) {
617 (Some((hd, vd, ctx, array)), Some((hd2, vd2, ctx2, array2))) => {
618 if (hd, vd, array) != (hd2, vd2, array2) {
619 return Err(format!("attention units {:?} and {:?}", (hd, vd, array), (hd2, vd2, array2)));
620 }
621 Some((hd, vd, ctx.max(ctx2), array))
622 }
623 (a, b) => a.or(b),
624 };
625 u.rows = u.rows.max(p.rows);
626 u.vector_rows = u.vector_rows.max(p.vector_rows);
627 u.side_rows = u.side_rows.max(p.side_rows);
628 u.pair_rows = u.pair_rows.max(p.pair_rows);
629 u.store_beats = u.store_beats.max(p.store_beats);
630 u.job_words = u.job_words.max(p.job_words);
631 u.tensors = u.tensors.max(p.tensors);
632 u.layers = u.layers.max(p.layers);
633 u.ropes = u.ropes.max(p.ropes);
634 u.folds |= p.folds;
635 for &(ty, blocks) in &p.formats {
636 match u.formats.iter_mut().find(|(t, _)| *t == ty) {
637 Some(f) => f.1 = f.1.max(blocks),
638 None => u.formats.push((ty, blocks)),
639 }
640 }
641 }
642 u.formats.sort_by_key(|(ty, _)| ty.id());
643 Ok(u)
644 }
645}
646
647pub fn compose(sources: &[&str]) -> String {
650 let mut seen: HashMap<String, String> = HashMap::new();
651 let mut out = String::new();
652 for src in sources {
653 let mut rest: &str = src;
654 loop {
655 let start = rest.match_indices("module ").map(|(i, _)| i).find(|&i| i == 0 || rest.as_bytes()[i - 1] == b'\n');
656 let Some(i) = start else {
657 if !rest.trim().is_empty() && !out.contains(rest.trim()) {
658 out.push_str(rest);
659 }
660 break;
661 };
662 let end = i + rest[i..].find("endmodule").expect("a module's end") + "endmodule".len();
663 let name: String = rest[i + 7..].chars().take_while(|c| c.is_alphanumeric() || *c == '_').collect();
664 let body = &rest[i..end];
665 match seen.get(&name) {
666 Some(prior) => assert_eq!(prior, body, "two sources define the module {name} two ways"),
667 None => {
668 seen.insert(name, body.to_string());
669 out.push_str(&rest[..end]);
670 out.push('\n');
671 }
672 }
673 rest = &rest[end..];
674 }
675 }
676 out
677}
678
679pub fn census(verilog: &str, top: &str) -> BTreeMap<String, usize> {
683 let mut bodies: HashMap<&str, &str> = HashMap::new();
684 let mut rest = verilog;
685 while let Some(i) = rest.match_indices("module ").map(|(i, _)| i).find(|&i| i == 0 || rest.as_bytes()[i - 1] == b'\n') {
686 let end = i + rest[i..].find("endmodule").expect("a module's end");
687 let name = &rest[i + 7..];
688 let name = &name[..name.find(|c: char| !(c.is_alphanumeric() || c == '_')).unwrap_or(name.len())];
689 bodies.insert(name, &rest[i..end]);
690 rest = &rest[end + "endmodule".len()..];
691 }
692 fn walk(module: &str, reach: usize, bodies: &HashMap<&str, &str>, out: &mut BTreeMap<String, usize>) {
693 let Some(body) = bodies.get(module) else { return };
694 for line in body.lines() {
695 let t = line.trim_start();
696 let child = &t[..t.find(|c: char| !(c.is_alphanumeric() || c == '_')).unwrap_or(t.len())];
697 let after = t[child.len()..].trim_start();
698 if bodies.contains_key(child) && after.starts_with(|c: char| c == '#' || c.is_alphabetic() || c == '_') {
699 *out.entry(child.to_string()).or_default() += reach;
700 walk(child, reach, bodies, out);
701 }
702 }
703 }
704 let mut out = BTreeMap::new();
705 walk(top, 1, &bodies, &mut out);
706 out
707}
708
709pub fn engine_verilog(p: &EngineParams) -> String {
713 assert!(
714 !p.folds || p.streaming.as_ref().is_some_and(|s| s.folds > 0),
715 "a step that folds its matrices' sums into their output slots runs on a streaming unit that folds them (Streaming::with_folds)"
716 );
717 let columns = if p.streaming.is_some() { &[][..] } else { &p.formats[..] };
718 let mut block_lens: Vec<usize> = columns.iter().map(|(ty, _)| ty.block_len()).collect();
719 if let Some((hd, _, _, true)) = p.attend {
721 block_lens.push(hd);
722 }
723 block_lens.sort();
724 block_lens.dedup();
725 let widest = p.formats.iter().map(|(ty, _)| 8 * ty.block_bytes()).max().unwrap_or(8);
726 let mut cases = String::new();
727 let mut units = String::new();
728 let mut ready = String::from("1'b0");
729 let mut col_busy = String::from("1'b0");
730 let mut out_mux = String::new();
731 for (i, (ty, blocks)) in columns.iter().enumerate() {
732 let name = ty.name().to_lowercase();
733 let (len, bytes) = (ty.block_len(), ty.block_bytes());
734 writeln!(cases, " 8'd{}: begin mv_known = 1'b1; mv_fmt = 4'd{i}; mv_blen = 16'd{len}; mv_bbytes = 16'd{bytes}; end", ty.id()).unwrap();
735 writeln!(
736 units,
737 r#" wire mv_busy_{i}, mv_ready_{i}, mv_ov_{i};
738 wire [15:0] mv_op_{i};
739 wire [7:0] mv_ot_{i};
740 wire [31:0] mv_o1_{i}, mv_o2_{i};
741 logos_matvec_engine_{name} #(.T_MAX(ROW_MAX), .NB_MAX({blocks})) col_{i}(.clk(clk), .rst(rst), .start(mv_start && mv_fmt == 4'd{i}),
742 .pairs(mv_pairs[15:0]), .blocks(mv_blocks[15:0]), .tokens(rows[7:0]), .two(two_words), .busy(mv_busy_{i}),
743 .act_wr(act_wr && mv_fmt == 4'd{i}), .act_t(act_t), .act_b(act_b), .act_codes(aq_codes[{codes_top}:0]), .act_m(aq_m), .act_e(aq_e),
744 .w_valid(w_valid && mv_fmt == 4'd{i}), .w_ready(mv_ready_{i}), .w_blk1(w_blk1[{top}:0]), .w_blk2(w_blk2[{top}:0]),
745 .o_valid(mv_ov_{i}), .o_pair(mv_op_{i}), .o_t(mv_ot_{i}), .o_val1(mv_o1_{i}), .o_val2(mv_o2_{i}));"#,
746 top = 8 * bytes - 1,
747 codes_top = 16 * len - 1
748 )
749 .unwrap();
750 ready = format!("{ready} | (mv_fmt == 4'd{i} && mv_ready_{i})");
751 col_busy = format!("{col_busy} | mv_busy_{i}");
752 writeln!(out_mux, " if (mv_fmt == 4'd{i}) begin o_v = mv_ov_{i}; o_p = mv_op_{i}; o_t = mv_ot_{i}; o_1 = mv_o1_{i}; o_2 = mv_o2_{i}; end").unwrap();
753 }
754 let attention_side = p.streaming.as_ref().is_some_and(|s| s.attention);
755 let (quantizers, feeds) = match block_lens.last() {
756 Some(_) if attention_side => (" wire aq_busy = 1'b0;\n".to_string(), String::new()),
757 Some(&lanes) => {
758 assert!(block_lens.iter().all(|len| len % 16 == 0), "blocks of {block_lens:?} are not whole beats");
759 let at_feeds = matches!(p.attend, Some((_, _, _, true)));
760 let shared = if p.streaming.is_some() { "the query heads share" } else { "every weight block and query head shares" };
761 (
762 format!(
763 r#" // The activation quantizer {shared}, fed a beat a cycle from the store.
764 wire aq_in, aq_clear, aq_go, aq_busy;
765 wire [7:0] aq_m;
766 wire signed [11:0] aq_e;
767 wire [{}:0] aq_codes;
768 logos_act_stream #(.LANES({lanes})) aq(.clk(clk), .rst(rst), .clear(aq_clear), .in_v(aq_in), .in_beat(st_rd), .go(aq_go),
769 .two(two_words), .busy(aq_busy), .m(aq_m), .e(aq_e), .codes(aq_codes));
770"#,
771 16 * lanes - 1
772 ),
773 if p.streaming.is_some() {
774 " // A query head's beats while it loads, its codes once the last is in.\n assign aq_in = state == AT_Q && at_lj != AH_BEATS;\n \
775 assign aq_clear = state == AT_Q && at_lj == 32'd0;\n assign aq_go = state == AT_Q && at_lj == AH_BEATS;\n"
776 .to_string()
777 } else {
778 format!(
779 r#" // A weight block's beats while it loads, its codes once the last is in; a query head's the same.
780 wire mv_aq = state == MV_ACT && mv_known && {{8'd0, act_t}} != {{8'd0, rows}};
781 wire mv_aq_in = mv_aq && aj < {{16'd0, mv_blen}} >> 4;
782 assign aq_in = mv_aq_in{at_in};
783 assign aq_clear = (mv_aq_in && aj == 32'd0){at_clear};
784 assign aq_go = (mv_aq && aj == {{16'd0, mv_blen}} >> 4){at_go};
785"#,
786 at_in = if at_feeds { " || (state == AT_Q && at_lj != AH_BEATS)" } else { "" },
787 at_clear = if at_feeds { " || (state == AT_Q && at_lj == 32'd0)" } else { "" },
788 at_go = if at_feeds { " || (state == AT_Q && at_lj == AH_BEATS)" } else { "" },
789 )
790 },
791 )
792 }
793 None => (" wire aq_busy = 1'b0;\n".to_string(), String::new()),
794 };
795 let fast_recurrence = p.streaming.as_ref().is_some_and(|s| s.recurrence);
796 assert!(!fast_recurrence || p.delta.is_some(), "a recurrence unit runs a model's Gated-DeltaNet blocks");
797 if let (true, Some((hd, _))) = (fast_recurrence, p.delta) {
798 assert!(hd * 2 % 64 == 0, "a recurrence unit walks a head's memory in whole lines, and a head {hd} wide is not whole lines in binary16");
799 }
800 let vectors = p.streaming.as_ref().is_some_and(|s| s.vectors);
801 let handed_off = vectors && p.delta.is_none_or(|(_, f)| recurrence_log(f).0 > 1);
804 if let Some((_, StateFlush::Trailing { .. })) = p.delta {
805 assert!(fast_recurrence && p.streaming.as_ref().is_some_and(|s| s.recurrence_lanes > 1), "a trailing flush runs on the recurrence's head lanes alone");
806 }
807 let normed = p.streaming.as_ref().is_some_and(|s| s.delta_norm);
808 if normed {
809 assert!(
810 p.delta.is_some_and(|(_, f)| recurrence_log(f).0 > 1),
811 "a recurrence makes its gated norms on its head lanes, which run the memories it flushes lazily alone"
812 );
813 }
814 let delta = p.delta.map_or_else(UnitVerilog::default, |(hd, f)| {
815 let t = recurrence_log(f).0;
816 if handed_off {
817 delta_handed_off(hd, t, normed)
818 } else if fast_recurrence {
819 let unit = delta_on_the_fast_side(delta_verilog(hd, t));
820 if normed { delta_normed_core(unit) } else { unit }
821 } else {
822 delta_verilog(hd, t)
823 }
824 });
825 let attention_side = p.streaming.as_ref().is_some_and(|s| s.attention);
826 assert!(!attention_side || matches!(p.attend, Some((_, _, _, true))), "the attention side attends over an int8 cache on the array");
827 let attend_unit = match p.attend {
828 Some(_) if attention_side => attend_handed_off(),
829 Some((hd, vd, ctx, array)) => attend_unit_verilog(hd, vd, ctx, array),
830 None => UnitVerilog::default(),
831 };
832 let delta_pulses = format!(
833 "{}{}",
834 match (p.delta.is_some(), fast_recurrence, handed_off) {
835 (true, false, _) => "dre_v <= 1'b0; dlr_v <= 1'b0; dlr_log_wr <= 1'b0; ",
836 (true, true, false) => "dre_v <= 1'b0; sm_gdn_go <= 1'b0; ",
837 (true, true, true) => "sm_gdn_go <= 1'b0; ",
838 (false, _, _) => "",
839 },
840 match (p.attend.is_some(), attention_side) {
841 (true, false) => "at_start <= 1'b0;",
842 (true, true) => "sm_att_go <= 1'b0;",
843 (false, _) => "",
844 }
845 );
846 let delta_reads = match (p.delta.is_some(), fast_recurrence, handed_off) {
847 (true, false, _) => {
848 "if (dl_rd) begin s_rd_reg = dl_rd_reg; s_rd_row = dl_rd_row; end\n if (state == DL_KQ_WAIT) s_rd_s = 4'd0;\n if (state == DL_FEXP_WAIT) s_rd_s = 4'd1;"
849 }
850 (true, true, false) => "if (state == DL_KQ_WAIT) s_rd_s = 4'd0;",
851 (_, _, true) | (false, _, _) => "",
852 };
853 let mv = match &p.streaming {
854 None => MatvecText::reference(),
855 Some(s) => MatvecText::hybrid(s),
856 };
857 let template = if vectors { vector_jobs_on_the_fast_side(ENGINE_VERILOG, handed_off) } else { ENGINE_VERILOG.to_string() };
858 let engine = template
859 .replace("@PORTS@", &mv.ports)
860 .replace("@STORE_RAM@", &mv.store_ram)
861 .replace("@MATVEC_DECLS@", &mv.decls)
862 .replace("@MATVEC_STATES@", &mv.states)
863 .replace("@MATVEC_PULSES@", &mv.pulses)
864 .replace("@MATVEC_CASES@", &mv.cases)
865 .replace("@MATVEC_READS@", &mv.reads)
866 .replace("@STORE_WRITE_NOTE@", &mv.write_note)
867 .replace("@MATVEC_WRITES@", &mv.writes)
868 .replace("@DELTA_DECLS@", &delta.decls)
869 .replace("@DELTA_CASES@", &delta.cases)
870 .replace("@STORE_READS@", &format!("{}{}", delta.reads, attend_unit.reads))
871 .replace("@STORE_WRITES@", &format!("{}{}", delta.writes, attend_unit.writes))
872 .replace("@DELTA_PULSES@", &delta_pulses)
873 .replace("@ATTEND_DECLS@", &attend_unit.decls)
874 .replace("@ATTEND_CASES@", &attend_unit.cases)
875 .replace("@ATTEND_UNIT@", if p.attend.is_some() { "4" } else { "15" })
876 .replace("@DELTA_READS@", delta_reads)
877 .replace("@DELTA_UNIT@", if p.delta.is_some() { "3" } else { "15" })
878 .replace("@ROWS@", &p.rows.to_string())
879 .replace("@VROWS@", &p.vector_rows.to_string())
880 .replace("@FU@", &p.function_units.to_string())
881 .replace("@STORE@", &p.store_beats.to_string())
882 .replace("@JOBS@", &p.job_words.to_string())
883 .replace("@TENSORS@", &p.tensors.max(1).to_string())
884 .replace("@LAYERS@", &p.layers.max(1).to_string())
885 .replace("@ROPES@", &p.ropes.max(1).to_string())
886 .replace("@WIDEST@", &(widest - 1).to_string())
887 .replace("@FORMAT_CASES@", &cases)
888 .replace("@QUANTIZERS@", &quantizers)
889 .replace("@QUANTIZER_FEEDS@", &feeds)
890 .replace("@COLUMNS@", &units)
891 .replace("@READY@", &ready)
892 .replace("@COLUMN_BUSY@", &col_busy)
893 .replace("@OUT_MUX@", &out_mux);
894 let simd = super::simd::simd_verilog();
895 let matvec = if p.formats.is_empty() || p.streaming.is_some() { String::new() } else { super::matvec::matvec_verilog() };
896 let gdn = if p.delta.is_some() { super::gdn::gdn_verilog() } else { String::new() };
897 let attend = if p.attend.is_some() { super::attend::attend_verilog() } else { String::new() };
898 let ram = super::ram::ram_verilog(&[64, 128, 512, 608]);
899 let act = super::act_quant::act_quant_verilog();
900 let core = engine.replacen("module logos_engine #(", "module logos_engine_core #(", 1);
901 let srl = if p.streaming.is_some() && reads_the_store_for_the_host_alone(&engine) { STORE_READ_LATENCY } else { 1 };
905 assert!(
906 p.streaming.as_ref().is_none_or(|s| s.contexts == 1) || !core.contains("wq_v <= 1'b1"),
907 "an engine of contexts shares the card's port for reads alone, and this core writes the card"
908 );
909 let enabled = enable(&compose(&[&ram, &simd, &matvec, &act, &gdn, &attend, &core]));
910 let fast = match &p.streaming {
911 Some(s) => {
912 let recurrence = if s.recurrence {
913 let rows = super::gdn::rows_verilog();
914 let rows = if s.delta_norm { super::gdn::delta_normed_rows(&rows) } else { rows };
915 let rows = if s.contexts > 1 { gdn_rows_contexts(&rows) } else { rows };
916 format!("\n{}\n{rows}\n{}", super::gdn::gdn_stream_verilog(), super::super::physical::fast_port_verilog())
917 } else {
918 String::new()
919 };
920 let recurrence = if s.recurrence_lanes > 1 {
921 let lanes = match p.delta {
922 Some((_, StateFlush::Trailing { tokens })) => super::gdn::lanes_trailing_verilog(tokens as usize),
923 _ => super::gdn::lanes_verilog(),
924 };
925 let lanes = match p.delta {
926 Some((hd, _)) if s.delta_norm => super::gdn::delta_normed_lanes(&lanes, hd, s.hard_float.map_or(FpUnit::Soft, |h| h.fp_unit)),
927 _ => lanes,
928 };
929 format!("{recurrence}\n{lanes}")
930 } else {
931 recurrence
932 };
933 let recurrence = if s.recurrence_lanes > 1 || s.contexts > 1 {
934 format!("{recurrence}\n{}", super::super::physical::channel_mux_verilog())
935 } else {
936 recurrence
937 };
938 let recurrence = if s.contexts > 1 {
939 format!("{recurrence}\n{}", super::super::physical::read_credit_verilog())
940 } else {
941 recurrence
942 };
943 let vector_side = || {
944 let text = super::vector_side::vector_side_verilog();
945 let text = if s.conv_lanes > 0 {
946 format!("{}\n{}", vector_side_conv(&text, s.conv_lanes, s.conv_beats), super::vector_side::conv_path_verilog())
947 } else {
948 text
949 };
950 if s.contexts > 1 {
951 vector_side_contexts(&text)
952 } else {
953 text
954 }
955 };
956 let recurrence = if s.vectors { format!("{recurrence}\n{}", vector_side()) } else { recurrence };
957 let recurrence = if s.attention { format!("{recurrence}\n{}", super::attend::attend_side_verilog()) } else { recurrence };
958 let bank = if srl > 1 { super::ram::store_verilog() } else { super::ram::bram_re_verilog(&[512]) };
959 format!(
960 "{}\n{bank}\n{}\n{}{recurrence}",
961 super::super::physical::pipe_verilog(),
962 super::streaming::streaming_verilog_folding(std::slice::from_ref(&s.set), &s.layout, s.folds),
963 fast_store(&fast_verilog(p, s), srl)
964 )
965 }
966 None => String::new(),
967 };
968 let engine = format!("{}{}", super::ram::RAMSTYLE, prune(&compose(&[&enabled, &fast, &bridge_of(&crossing_ports(p)), &top_verilog(p)]), "logos_engine"));
969 match p.streaming.as_ref().and_then(|s| s.hard_float) {
970 Some(hard) => hard_float_verilog(&engine, &hard),
971 None => engine,
972 }
973}
974
975pub fn hard_float_verilog(verilog: &str, hard: &HardFloat) -> String {
980 let core = census(verilog, "logos_engine_core_ce");
981 for unit in ["logos_fmul_p", "logos_fadd_p"] {
982 assert!(!core.contains_key(unit), "the engine's core reaches `{unit}`, which the floating-point block's wrapper runs free of its enables");
983 }
984 let wrapper = |op, unit, block, latency| hard_verilog(op, unit, block, latency).unwrap_or_else(|e| panic!("{unit} on the block: {e}"));
985 modules(verilog)
986 .into_iter()
987 .map(|(name, text)| {
988 let head = &text[..text.find(&format!("module {name}")).unwrap_or(text.len())];
989 match name.as_str() {
990 "logos_fmul_p" => match &hard.product {
991 Some(product) => format!(
992 "{head}{}",
993 hard_verilog_for(fp_hard::Op::Multiply, "logos_fmul_p", product, FMUL_LATENCY, &hard.product_target())
994 .unwrap_or_else(|e| panic!("logos_fmul_p on the block: {e}"))
995 ),
996 None => format!("{text}\n"),
997 },
998 "logos_fadd_p" => format!("{head}{}", wrapper(fp_hard::Op::Add, "logos_fadd_p", &hard.sum, FADD_LATENCY)),
999 _ => format!("{text}\n"),
1000 }
1001 })
1002 .collect()
1003}
1004
1005pub fn hard_float_model(hard: &HardFloat) -> String {
1010 let (m, a) = (hard.product.unwrap_or(hard.sum), hard.sum);
1011 hard_model_verilog(&m.semantics, &a.semantics, Depths { full: [m.full, a.full, m.full.max(a.full)], least: [m.least, a.least] })
1012}
1013
1014fn reads_the_store_for_the_host_alone(core: &str) -> bool {
1017 core.matches("st_ra = ").count() == 1 && core.contains(" default: st_ra = host_raddr;\n")
1018}
1019
1020fn fast_store(text: &str, srl: usize) -> String {
1024 if srl == 1 {
1025 return text.to_string();
1026 }
1027 assert_eq!(srl, STORE_READ_LATENCY, "a store reads in one clock or in the pipelined bank's");
1028 assert_eq!(text.matches("logos_bram_re_w512 #(").count(), 1, "the fast side's one store instance");
1029 let mut t = text.replace("logos_bram_re_w512 #(", "logos_store_w512 #(");
1030 let given = |t: &mut String, from: &str| -> bool {
1032 let Some(at) = t.find(from) else { return false };
1033 let open = at + t[at..].find("#(").expect("an instance's parameters") + 1;
1034 let mut depth = 0;
1035 let close = open
1036 + t[open..]
1037 .char_indices()
1038 .find(|&(_, c)| {
1039 depth += match c {
1040 '(' => 1,
1041 ')' => -1,
1042 _ => 0,
1043 };
1044 depth == 0
1045 })
1046 .expect("an instance's parameters closed")
1047 .0;
1048 t.insert_str(close, &format!(", .SRL({srl})"));
1049 true
1050 };
1051 assert!(given(&mut t, " logos_streaming_matvec_"), "the fast side's streaming unit");
1052 let mut readers = Vec::new();
1053 for reader in ["logos_vector_side", "logos_gdn_side", "logos_gdn_lanes", "logos_attend_side"] {
1054 let from = format!(" {reader} #(");
1055 assert!(t.matches(&from).count() <= 1, "one {reader} on the fast side");
1056 if given(&mut t, &from) {
1057 readers.push(reader);
1058 }
1059 }
1060 for reader in ["logos_vector_side", "logos_attend_side"] {
1061 assert!(readers.contains(&reader), "a fast side whose core reads the store for the host alone runs its {reader}");
1062 }
1063 assert!(
1064 readers.contains(&"logos_gdn_side") || readers.contains(&"logos_gdn_lanes"),
1065 "a fast side whose core reads the store for the host alone runs its recurrence"
1066 );
1067 t
1068}
1069
1070fn bridged_map(p: &EngineParams) -> Vec<(bool, String, String)> {
1073 if !p.streaming.as_ref().is_some_and(|s| s.recurrence) {
1074 return Vec::new();
1075 }
1076 vec![
1077 (true, "[31:0]".to_string(), "map_space".to_string()),
1078 (true, format!("[{}:0]", super::stream::AW - 1), "map_base".to_string()),
1079 (true, "[4:0]".to_string(), "map_granule_log2".to_string()),
1080 ]
1081}
1082
1083
1084pub fn simulation_twin(verilog: &str) -> String {
1093 let mut reached: Vec<String> = census(verilog, "logos_engine_core_ce").into_keys().collect();
1094 reached.push("logos_engine_core_ce".to_string());
1095 for (name, text) in modules(verilog).iter().filter(|(n, _)| reached.contains(n)) {
1096 let body = &text[text.find(&format!("module {name}")).expect("its header")..];
1097 let always = body.matches("always @(").count();
1098 if name.starts_with("logos_ram_ce_w") {
1099 let (read, write) = (" always @(posedge clk) if (cr) rd <= mem[ra];\n", " always @(posedge clk)\n if (ce && we) begin\n");
1100 assert!(always == 2 && body.contains(read) && body.contains(write), "`{name}` reads under cr and writes under ce alone");
1101 } else if body.contains("(input clk, input step_en, input read_en, input rst,") {
1102 assert_eq!(body.matches("always @(posedge clk) if (step_en) begin").count(), always, "`{name}`'s every block steps under step_en");
1103 } else {
1104 assert!(!body.contains("posedge") && !body.contains("negedge") && !body.contains("input clk"), "`{name}` holds no state of its own");
1105 }
1106 assert_eq!(body.matches(".clk(").count(), body.matches(".clk(clk)").count(), "`{name}` clocks its instances by its own clock");
1107 let enabled = body.matches("(.clk(clk), .ce(step_en), .cr(read_en), ").count()
1108 + body.matches("(.clk(clk), .step_en(step_en), .read_en(read_en), ").count();
1109 assert_eq!(body.matches(".clk(clk)").count(), enabled, "`{name}` enables its every instance by its own step_en and read_en");
1110 }
1111 let instances: Vec<String> = verilog
1113 .match_indices(" logos_engine_core_ce ")
1114 .map(|(at, head)| {
1115 let rest = &verilog[at + head.len()..];
1116 let name: String = rest.chars().take_while(|c| c.is_alphanumeric() || *c == '_').collect();
1117 assert!(
1118 rest[name.len()..].starts_with("(.clk(fclk), .step_en(fedge), .read_en(cr),"),
1119 "the core's instance `{name}` clocked by the user clock, stepping at fedge and reading at cr"
1120 );
1121 name
1122 })
1123 .collect();
1124 assert!(instances.first().is_some_and(|n| n == "core" || n == "core0"), "the core's instances: {instances:?}");
1125 assert!(instances.len() == 1 || (0..instances.len()).all(|k| instances[k] == format!("core{k}")), "the cores' instances: {instances:?}");
1126 let mut twin = verilog.to_string();
1127 for (k, name) in instances.iter().enumerate() {
1128 let from = format!(" logos_engine_core_ce {name}(.clk(fclk), .step_en(fedge), .read_en(cr),");
1129 let gate = if k == 0 {
1130 " // The simulator's twin: the core's clock without the user cycles it neither steps nor reads its memories in.\n \
1131 reg core_gate = 1'b0;\n always @(negedge fclk) core_gate <= fedge || cr;\n wire core_clk = fclk && core_gate;\n"
1132 } else {
1133 ""
1134 };
1135 twin = twin.replacen(&from, &format!("{gate} logos_engine_core_ce {name}(.clk(core_clk), .step_en(fedge), .read_en(cr),"), 1);
1136 }
1137 twin
1138}
1139
1140fn prune(verilog: &str, top: &str) -> String {
1143 let mut reached: Vec<String> = census(verilog, top).into_keys().collect();
1144 reached.push(top.to_string());
1145 modules(verilog)
1146 .into_iter()
1147 .map(|(name, text)| {
1148 if name.is_empty() || reached.contains(&name) {
1149 format!("{text}\n")
1150 } else {
1151 text[..text.find(&format!("module {name}")).expect("its header")].to_string()
1152 }
1153 })
1154 .collect()
1155}
1156
1157fn modules(verilog: &str) -> Vec<(String, String)> {
1159 let mut out = Vec::new();
1160 let mut rest = verilog;
1161 loop {
1162 let start = rest.match_indices("module ").map(|(i, _)| i).find(|&i| i == 0 || rest.as_bytes()[i - 1] == b'\n');
1163 let Some(i) = start else {
1164 if !rest.trim().is_empty() {
1165 out.push((String::new(), rest.to_string()));
1166 }
1167 return out;
1168 };
1169 let end = i + rest[i..].find("endmodule").expect("a module's end") + "endmodule".len();
1170 let name: String = rest[i + 7..].chars().take_while(|c| c.is_alphanumeric() || *c == '_').collect();
1171 out.push((name, rest[..end].to_string()));
1172 rest = &rest[end..];
1173 }
1174}
1175
1176fn enable_instances(text: &str, pattern: &str, to: &str, enables: &str) -> String {
1179 let mut out = String::new();
1180 let mut rest = text;
1181 while let Some(i) = rest.find(pattern) {
1182 out.push_str(&rest[..i]);
1183 out.push_str(to);
1184 rest = &rest[i + pattern.len()..];
1185 let clk = rest.find("(.clk(clk), ").expect("an instance's clock");
1186 assert!(!rest[..clk].contains(';'), "the clock of the instance `{pattern}` in its statement");
1187 out.push_str(&rest[..clk]);
1188 out.push_str("(.clk(clk), ");
1189 out.push_str(enables);
1190 rest = &rest[clk + "(.clk(clk), ".len()..];
1191 }
1192 out.push_str(rest);
1193 out
1194}
1195
1196fn enable(verilog: &str) -> String {
1204 let parts = modules(verilog);
1205 let is_ram = |n: &str| n.starts_with("logos_ram_w");
1206 let mut reached: Vec<String> = census(verilog, "logos_engine_core").into_keys().collect();
1207 reached.push("logos_engine_core".to_string());
1208 let clocked: Vec<String> =
1209 parts.iter().filter(|(n, t)| reached.contains(n) && !is_ram(n) && t.contains("(input clk,")).map(|(n, _)| n.clone()).collect();
1210 let mut widths: Vec<usize> = Vec::new();
1211 let mut out = String::new();
1212 for (name, text) in &parts {
1213 if is_ram(name) || (!name.is_empty() && !reached.contains(name)) {
1216 out.push_str(&text[..text.find(&format!("module {name}")).expect("its header")]);
1217 continue;
1218 }
1219 let mut t = text.clone();
1220 assert!(!t.contains("step_en") && !t.contains("read_en"), "`{name}` names an enable");
1221 if clocked.contains(name) {
1222 let header = t.find(&format!("module {name}")).expect("its header") + "module ".len() + name.len();
1223 t.insert_str(header, "_ce");
1224 assert_eq!(t.matches("(input clk, input rst,").count(), 1, "`{name}`'s clock and reset");
1225 t = t.replacen("(input clk, input rst,", "(input clk, input step_en, input read_en, input rst,", 1);
1226 let blocks = t.matches("always @(posedge clk) begin").count();
1227 assert!(blocks >= 1 && t.matches("always @(").count() == blocks, "`{name}`'s every clocked block a rising-edge block");
1228 t = t.replace("always @(posedge clk) begin", "always @(posedge clk) if (step_en) begin");
1229 }
1230 for n in &clocked {
1231 t = enable_instances(&t, &format!("{n} #("), &format!("{n}_ce #("), ".step_en(step_en), .read_en(read_en), ");
1232 }
1233 while let Some(i) = t.find("logos_ram_w") {
1234 let digits: String = t[i + "logos_ram_w".len()..].chars().take_while(|c| c.is_ascii_digit()).collect();
1235 let w: usize = digits.parse().expect("a memory's width");
1236 if !widths.contains(&w) {
1237 widths.push(w);
1238 }
1239 let tail = enable_instances(&t[i..], &format!("logos_ram_w{w} #("), &format!("{} #(", super::ram::ram_ce_name(w)), ".ce(step_en), .cr(read_en), ");
1240 t = format!("{}{tail}", &t[..i]);
1241 }
1242 out.push_str(&t);
1243 out.push('\n');
1244 }
1245 widths.sort();
1246 format!("{}\n{out}", super::ram::ram_ce_verilog(&widths))
1247}
1248
1249fn engine_port_decls() -> Vec<(bool, String, String)> {
1251 let (text, _) = engine_ports();
1252 text.split(',')
1253 .map(|d| {
1254 let d = d.trim().trim_start_matches('(').trim();
1255 let input = d.starts_with("input");
1256 let name = d.split_whitespace().last().expect("a port's name").to_string();
1257 let width = d.find('[').map_or(String::new(), |a| d[a..=d.find(']').expect("a width's end")].to_string());
1258 (input, width, name)
1259 })
1260 .collect()
1261}
1262
1263pub fn bridge_verilog() -> String {
1270 bridge_of(&engine_port_decls())
1271}
1272
1273fn crossing_ports(p: &EngineParams) -> Vec<(bool, String, String)> {
1277 let contexts = p.streaming.as_ref().map_or(1, |s| s.contexts);
1278 let mut ports: Vec<(bool, String, String)> = engine_port_decls()
1279 .into_iter()
1280 .map(|(input, width, name)| if name == "rows" && contexts > 1 { (input, format!("[{}:0]", 16 * contexts - 1), name) } else { (input, width, name) })
1281 .collect();
1282 ports.extend(bridged_map(p));
1283 ports
1284}
1285
1286fn bridge_of(crossing: &[(bool, String, String)]) -> String {
1288 let mut ports = Vec::new();
1289 let mut regs = String::new();
1290 let (mut caps, mut helds, mut assigns) = (String::new(), String::new(), String::new());
1291 for (input, width, name) in crossing.iter().cloned() {
1292 if name == "clk" {
1293 continue;
1294 }
1295 let w = if width.is_empty() { String::new() } else { format!("{width} ") };
1296 if input {
1297 ports.push(format!("input {w}s_{name}, output {w}f_{name}"));
1298 writeln!(regs, " reg {w}cap_{name};").unwrap();
1299 if name == "rst" {
1300 writeln!(caps, " if (frst) cap_rst <= 1'b1; else if (emark2) cap_rst <= s_rst;").unwrap();
1301 } else {
1302 writeln!(caps, " if (emark2) cap_{name} <= s_{name};").unwrap();
1303 }
1304 writeln!(assigns, " assign f_{name} = cap_{name};").unwrap();
1305 } else {
1306 ports.push(format!("output {w}s_{name}, input {w}f_{name}"));
1307 writeln!(regs, " reg {w}held_{name};").unwrap();
1308 writeln!(helds, " if (mark2) held_{name} <= f_{name};").unwrap();
1309 writeln!(assigns, " assign s_{name} = held_{name};").unwrap();
1310 }
1311 }
1312 format!(
1313 r#"// The crossing between the engine clock the shell runs in (sclk) and the user clock it is divided from (fclk), every
1314// register of it in the user clock (fedge and fmid high the user cycles that end with the engine clock's rising and
1315// falling edges): each value the shell gives the engine taken straight into a register (cap_*) two user cycles after the
1316// rising edge, each value the engine gives the shell taken straight into a register (held_*) two user cycles after the
1317// falling edge. With the engine clock {min} user cycles or more, a path from the shell into a cap_* register has two
1318// user cycles and one from a held_* register into the shell half an engine cycle less two user cycles.
1319module logos_engine_bridge
1320 (input sclk, input fclk, input frst, input fedge, input fmid,
1321 {ports});
1322{regs} reg emark1, emark2, mark1, mark2;
1323 always @(posedge fclk) begin
1324 emark1 <= fedge;
1325 emark2 <= emark1;
1326 mark1 <= fmid;
1327 mark2 <= mark1;
1328{caps}{helds} end
1329{assigns}endmodule
1330"#,
1331 min = ENGINE_MIN_DIVIDE,
1332 ports = ports.join(",\n "),
1333 )
1334}
1335
1336fn top_verilog(p: &EngineParams) -> String {
1342 let (clk2_in, clk2_pass) = if p.streaming.as_ref().is_some_and(|s| s.set.pe_pairs) { (" input clk2,", ".clk2(clk2), ") } else { ("", "") };
1344 if let Some(s) = p.streaming.as_ref().filter(|s| s.contexts > 1) {
1345 return top_contexts_verilog(p, s);
1346 }
1347 let decls = engine_port_decls();
1348 let (ports, _) = engine_ports();
1349 let mut wires = String::new();
1350 let mut bridge = vec![".sclk(clk), .fclk(fclk), .frst(frst), .fedge(fedge), .fmid(fmid)".to_string()];
1351 let mut core = vec![".clk(fclk), .step_en(fedge), .read_en(cr)".to_string()];
1352 for (_, width, name) in &decls {
1353 if name == "clk" {
1354 continue;
1355 }
1356 let w = if width.is_empty() { String::new() } else { format!("{width} ") };
1357 writeln!(wires, " wire {w}f_{name};").unwrap();
1358 bridge.push(format!(".s_{name}({name}), .f_{name}(f_{name})"));
1359 core.push(format!(".{name}(f_{name})"));
1360 }
1361 for (_, width, name) in &bridged_map(p) {
1362 let w = if width.is_empty() { String::new() } else { format!("{width} ") };
1363 writeln!(wires, " wire {w}f_{name};").unwrap();
1364 bridge.push(format!(".s_{name}({name}), .f_{name}(f_{name})"));
1365 }
1366 let (fast_ports, fast) = match &p.streaming {
1367 None => (String::new(), String::new()),
1368 Some(s) => {
1369 use super::stream::{AW, LW};
1370 let sa = store_address_bits(p);
1371 let (j, mdt, drt) = (HybridJob::BITS, 4 * (AW + LW + 1) - 1, 4 * 8 * s.layout.line_bytes - 1);
1372 core.push(".sm_own(own), .sm_go(go), .sm_job(job), .sm_busy(held_busy), .e_ra(e_ra), .e_we(e_we), .e_wa(e_wa), .e_wd(e_wd), .e_wm(e_wm), .e_rd(e_rd)".to_string());
1373 if s.vectors {
1374 core.push(".sm_vec_go(vec_go), .sm_vec_job(vec_job), .sm_vec_fault(vec_fault)".to_string());
1375 }
1376 if s.attention {
1377 core.push(".sm_att_go(att_go), .sm_att_job(att_job)".to_string());
1378 }
1379 let (attention_wires, attention) = if s.attention {
1380 (format!(" wire att_go;\n wire [{}:0] att_job;\n", AttendJob::BITS - 1), ",\n .att_go(att_go), .att_job(att_job)".to_string())
1381 } else {
1382 (String::new(), String::new())
1383 };
1384 let (vector_wires, vectors) = if s.vectors {
1385 (
1386 format!(" wire vec_go, vec_fault;\n wire [{}:0] vec_job;\n", VectorSideJob::BITS - 1),
1387 ",\n .vec_go(vec_go), .vec_job(vec_job), .vec_fault(vec_fault)"
1388 .to_string(),
1389 )
1390 } else {
1391 (String::new(), String::new())
1392 };
1393 let (recurrence_ports, recurrence_wires, recurrence) = if s.recurrence {
1394 core.push(".sm_gdn_go(gdn_go), .sm_gdn_job(gdn_job), .sm_gdn_fault(gdn_fault)".to_string());
1395 (
1396 format!(
1397 ",\n output [3:0] sm_mw_valid, input [3:0] sm_mw_ready, output [{drt}:0] sm_mw_data,\n \
1398 input [3:0] sm_mdone_valid, output [3:0] sm_mdone_ready,\n \
1399 input [31:0] map_space, input [{aw}:0] map_base, input [4:0] map_granule_log2",
1400 aw = AW - 1
1401 ),
1402 format!(
1403 " wire gdn_go, gdn_fault;\n wire [{}:0] gdn_job;\n wire [3:0] p_mw_valid, p_mw_ready, p_mdone_valid, p_mdone_ready;\n \
1404 wire [{drt}:0] p_mw_data;\n",
1405 GdnJob::bits(s) - 1
1406 ),
1407 ",\n .mw_valid(p_mw_valid), .mw_ready(p_mw_ready), .mw_data(p_mw_data), .mdone_valid(p_mdone_valid),\n \
1408 .mdone_ready(p_mdone_ready), .gdn_go(gdn_go), .gdn_job(gdn_job), .gdn_fault(gdn_fault),\n \
1409 .host_wr(f_host_wr), .host_table(f_host_table), .host_addr(f_host_addr), .host_wdata(f_host_wdata),\n \
1410 .map_space(f_map_space), .map_base(f_map_base), .map_granule_log2(f_map_granule_log2)"
1411 .to_string(),
1412 )
1413 } else {
1414 (String::new(), String::new(), String::new())
1415 };
1416 (
1417 format!(
1418 ",\n output [3:0] sm_md_valid, input [3:0] sm_md_ready, output [{mdt}:0] sm_md_data,\n input [3:0] sm_mr_valid, output [3:0] sm_mr_ready, input [{drt}:0] sm_mr_data{recurrence_ports}"
1419 ),
1420 format!(
1421 r#" wire own, go, held_busy, e_we;
1422 wire [{jt}:0] job;
1423 wire [31:0] e_ra, e_wa;
1424 wire [511:0] e_wd, e_rd;
1425 wire [63:0] e_wm;
1426{recurrence_wires}{vector_wires}{attention_wires}{pipes} logos_engine_fast fast(.clk(fclk), {clk2_pass}.rst(frst), .ce(fedge), .cr(cr), .own(own), .go(go), .job(job), .e_ra(e_ra[{st}:0]),
1427 .e_we(e_we), .e_wa(e_wa[{st}:0]), .e_wd(e_wd), .e_wm(e_wm), .rd(e_rd), .busy_held(held_busy), .md_valid(p_md_valid),
1428 .md_ready(p_md_ready), .md_data(p_md_data), .mr_valid(p_mr_valid), .mr_ready(p_mr_ready), .mr_data(p_mr_data){recurrence}{vectors}{attention});
1429"#,
1430 pipes = channel_pipes(s),
1431 jt = j - 1,
1432 st = sa - 1,
1433 ),
1434 )
1435 }
1436 };
1437 format!(
1438 r#"// The engine: its core stepping in the user clock (fclk) at the engine clock's rising edges (fedge) and reading its
1439// memories the user cycle after the falling edges (cr), the shell's values and its own through the crossing between the
1440// engine clock (clk) and the user clock{hybrid}.
1441module logos_engine
1442{top_ports},
1443 input fclk,{clk2_in} input frst, input fedge, input fmid{fast_ports});
1444 // The cycle after the engine clock's falling edge, the core's memories' read: a register its loads are spread from.
1445 (* maxfan = 2000 *) reg cr;
1446 always @(posedge fclk) cr <= fmid;
1447{wires} logos_engine_bridge bridge({bridge});
1448{fast} logos_engine_core_ce core({core});
1449endmodule
1450"#,
1451 hybrid = match &p.streaming {
1452 Some(s) if s.vectors => "; its matvec jobs, its lazily flushed memories' token-rows and its vector jobs on the units in the user clock",
1453 Some(s) if s.recurrence => "; its matvec jobs and its lazily flushed memories' token-rows on the units in the user clock",
1454 Some(_) => "; its matvec jobs on the streaming unit in the user clock",
1455 None => "",
1456 },
1457 top_ports = ports.replace("output reg ", "output "),
1458 bridge = bridge.join(",\n "),
1459 core = core.join(",\n "),
1460 )
1461}
1462
1463fn top_contexts_verilog(p: &EngineParams, s: &Streaming) -> String {
1471 let (clk2_in, clk2_pass) = if s.set.pe_pairs { (" input clk2,", ".clk2(clk2), ") } else { ("", "") };
1473 let c = s.contexts;
1474 let cb = if c > 1 { (usize::BITS - (c - 1).leading_zeros()) as usize } else { 1 };
1475 let sa = store_address_bits(p);
1476 let (j, g, v, a) = (HybridJob::BITS, GdnJob::bits(s), VectorSideJob::BITS, AttendJob::BITS);
1477 let drt = 4 * 8 * s.layout.line_bytes as usize - 1;
1478 let (ports, _) = engine_ports();
1479 assert_eq!(ports.matches("input [15:0] rows,").count(), 1, "the engine's rows port");
1480 let top_ports = ports.replace("input [15:0] rows,", &format!("input [{}:0] rows,", 16 * c - 1)).replace("output reg ", "output ");
1481 let mut wires = String::new();
1482 let mut bridge = vec![".sclk(clk), .fclk(fclk), .frst(frst), .fedge(fedge), .fmid(fmid)".to_string()];
1483 for (_, width, name) in &crossing_ports(p) {
1484 if name == "clk" {
1485 continue;
1486 }
1487 let w = if width.is_empty() { String::new() } else { format!("{width} ") };
1488 writeln!(wires, " wire {w}f_{name};").unwrap();
1489 bridge.push(format!(".s_{name}({name}), .f_{name}(f_{name})"));
1490 }
1491 let field = |bits: usize, k: usize| format!("[{}:{}]", bits * k + bits - 1, bits * k);
1492 let mut cores = String::new();
1493 for k in 0..c {
1494 let mut conns = vec![".clk(fclk), .step_en(fedge), .read_en(cr)".to_string()];
1495 for (_, _, name) in engine_port_decls() {
1496 if name == "clk" {
1497 continue;
1498 }
1499 let to = match name.as_str() {
1500 "start" => format!("f_start && f_rows{} != 16'd0", field(16, k)),
1501 "rows" => format!("f_rows{}", field(16, k)),
1502 "busy" => format!("c_busy[{k}]"),
1503 "fault" => format!("c_fault[{k}]"),
1504 "pc" => format!("c_pc{}", field(32, k)),
1505 "host_wr" => format!("c_host_wr[{k}]"),
1506 "host_addr" => "c_host_addr".to_string(),
1507 "host_raddr" => "c_host_raddr".to_string(),
1508 "host_rdata" => format!("c_host_rdata{}", field(512, k)),
1509 "mem_rd" => format!("c_mem_rd[{k}]"),
1510 "mem_rd_line" => format!("c_mem_rd_line{}", field(32, k)),
1511 "mem_rd_valid" => format!("c_mem_rd_valid[{k}]"),
1512 "mem_wr" => format!("c_mem_wr[{k}]"),
1513 "mem_wr_line" => format!("c_mem_wr_line{}", field(32, k)),
1514 "mem_wr_data" => format!("c_mem_wr_data{}", field(512, k)),
1515 "mem_wr_mask" => format!("c_mem_wr_mask{}", field(64, k)),
1516 "mem_wr_ready" => "1'b0".to_string(),
1517 _ => format!("f_{name}"),
1518 };
1519 conns.push(format!(".{name}({to})"));
1520 }
1521 conns.push(format!(
1522 ".sm_own(own[{k}]), .sm_go(go[{k}]), .sm_job(job{jf}), .sm_busy(held_busy[{k}]), .e_ra(e_ra{e32}), .e_we(e_we[{k}]),\n \
1523 .e_wa(e_wa{e32}), .e_wd(e_wd{e512}), .e_wm(e_wm{e64}), .e_rd(e_rd{e512})",
1524 jf = field(j, k),
1525 e32 = field(32, k),
1526 e512 = field(512, k),
1527 e64 = field(64, k),
1528 ));
1529 conns.push(format!(".sm_vec_go(vec_go[{k}]), .sm_vec_job(vec_job{}), .sm_vec_fault(vec_fault[{k}])", field(v, k)));
1530 conns.push(format!(".sm_att_go(att_go[{k}]), .sm_att_job(att_job{})", field(a, k)));
1531 conns.push(format!(".sm_gdn_go(gdn_go[{k}]), .sm_gdn_job(gdn_job{}), .sm_gdn_fault(gdn_fault[{k}])", field(g, k)));
1532 writeln!(cores, " logos_engine_core_ce core{k}({});", conns.join(",\n ")).unwrap();
1533 writeln!(cores, " assign c_host_wr[{k}] = f_host_wr && (host_shared || f_host_addr[31:28] == 4'd{k});").unwrap();
1534 }
1535 let packed = |name: &str| (0..c).rev().map(|k| format!("{name}[{}:{}]", 32 * k + sa - 1, 32 * k)).collect::<Vec<_>>().join(", ");
1536 let pcs = (0..c).rev().map(|k| format!("c_pc[{}:{}]", 32 * k + 15, 32 * k)).collect::<Vec<_>>().join(", ");
1537 format!(
1538 r#"// The engine of {c} contexts: a core a context stepping in the user clock (fclk) at the engine clock's rising edges
1539// (fedge) and reading its memories the user cycle after the falling edges (cr), the shell's values and the engine's
1540// through the crossing between the engine clock (clk) and the user clock; the units in the user clock, each class of
1541// them serving one context at a time.
1542module logos_engine
1543{top_ports},
1544 input fclk,{clk2_in} input frst, input fedge, input fmid,
1545 output [3:0] sm_md_valid, input [3:0] sm_md_ready, output [{mdt}:0] sm_md_data,
1546 input [3:0] sm_mr_valid, output [3:0] sm_mr_ready, input [{drt}:0] sm_mr_data,
1547 output [3:0] sm_mw_valid, input [3:0] sm_mw_ready, output [{drt}:0] sm_mw_data,
1548 input [3:0] sm_mdone_valid, output [3:0] sm_mdone_ready,
1549 input [31:0] map_space, input [{awt}:0] map_base, input [4:0] map_granule_log2);
1550 // The cycle after the engine clock's falling edge, the core's memories' read: a register its loads are spread from.
1551 (* maxfan = 2000 *) reg cr;
1552 always @(posedge fclk) cr <= fmid;
1553{wires} logos_engine_bridge bridge({bridge});
1554 wire [{ct}:0] own, go, held_busy, e_we;
1555 wire [{cj}:0] job;
1556 wire [{c32}:0] e_ra, e_wa;
1557 wire [{c512}:0] e_wd, e_rd;
1558 wire [{c64}:0] e_wm;
1559 wire [{ct}:0] gdn_go, gdn_fault;
1560 wire [{cg}:0] gdn_job;
1561 wire [3:0] p_mw_valid, p_mw_ready, p_mdone_valid, p_mdone_ready;
1562 wire [{drt}:0] p_mw_data;
1563 wire [{ct}:0] vec_go, vec_fault;
1564 wire [{cv}:0] vec_job;
1565 wire [{ct}:0] att_go;
1566 wire [{ca}:0] att_job;
1567{pipes} logos_engine_fast fast(.clk(fclk), {clk2_pass}.rst(frst), .ce(fedge), .cr(cr), .own(own), .go(go), .job(job), .e_ra({{{e_ra}}}),
1568 .e_we(e_we), .e_wa({{{e_wa}}}), .e_wd(e_wd), .e_wm(e_wm), .rd(e_rd), .busy_held(held_busy), .md_valid(p_md_valid),
1569 .md_ready(p_md_ready), .md_data(p_md_data), .mr_valid(p_mr_valid), .mr_ready(p_mr_ready), .mr_data(p_mr_data),
1570 .mw_valid(p_mw_valid), .mw_ready(p_mw_ready), .mw_data(p_mw_data), .mdone_valid(p_mdone_valid),
1571 .mdone_ready(p_mdone_ready), .gdn_go(gdn_go), .gdn_job(gdn_job), .gdn_fault(gdn_fault), .map_space(f_map_space),
1572 .map_base(f_map_base), .map_granule_log2(f_map_granule_log2),
1573 .vec_go(vec_go), .vec_job(vec_job), .vec_fault(vec_fault), .host_wr(f_host_wr), .host_table(f_host_table),
1574 .host_addr(f_host_addr), .host_wdata(f_host_wdata),
1575 .att_go(att_go), .att_job(att_job));
1576 // Each core takes the host's writes to the tables every context shares, and those to its own rows table and bank
1577 // of the store, the context in the host address's top four bits, cleared in the core's.
1578 wire host_shared = f_host_table != 3'd1 && f_host_table < 3'd6;
1579 wire [{ct}:0] c_host_wr, c_busy, c_fault, c_mem_rd, c_mem_wr, c_mem_rd_valid;
1580 wire [31:0] c_host_addr = {{4'd0, f_host_addr[27:0]}};
1581 wire [31:0] c_host_raddr = {{4'd0, f_host_raddr[27:0]}};
1582 wire [{c512}:0] c_host_rdata, c_mem_wr_data;
1583 wire [{c32}:0] c_pc, c_mem_rd_line, c_mem_wr_line;
1584 wire [{c64}:0] c_mem_wr_mask;
1585{cores} assign f_host_rdata = c_host_rdata[512*f_host_raddr[{cbh}:28] +: 512];
1586 assign f_busy = |c_busy;
1587 assign f_fault = |c_fault;
1588 assign f_pc = {{{pcs}}};
1589 // The cores' reads of the card through its one port, a read at a time.
1590 function [{cbt}:0] after(input [{ct}:0] want, input [{cbt}:0] last);
1591 integer i, k;
1592 reg found;
1593 begin
1594 found = 1'b0;
1595 after = last;
1596 for (i = 1; i <= {c}; i = i + 1) begin
1597 k = (last + i) % {c};
1598 if (!found && want[k]) begin found = 1'b1; after = k[{cbt}:0]; end
1599 end
1600 end
1601 endfunction
1602 reg [{ct}:0] rd_pend;
1603 reg [{c32}:0] rd_pend_line;
1604 reg rd_out;
1605 reg [{cbt}:0] rd_owner;
1606 wire [{ct}:0] rd_want = rd_pend | c_mem_rd;
1607 wire [{cbt}:0] rd_to = after(rd_want, rd_owner);
1608 wire rd_issue = rd_want != {c}'d0 && (!rd_out || f_mem_rd_valid);
1609 assign f_mem_rd = rd_issue;
1610 assign f_mem_rd_line = rd_pend[rd_to] ? rd_pend_line[32*rd_to +: 32] : c_mem_rd_line[32*rd_to +: 32];
1611 genvar rd_at;
1612 generate for (rd_at = 0; rd_at < {c}; rd_at = rd_at + 1) begin : rd_route
1613 assign c_mem_rd_valid[rd_at] = rd_out && f_mem_rd_valid && rd_owner == rd_at;
1614 end endgenerate
1615 assign f_mem_wr = 1'b0;
1616 assign f_mem_wr_line = 32'd0;
1617 assign f_mem_wr_data = 512'd0;
1618 assign f_mem_wr_mask = 64'd0;
1619 integer rq;
1620 always @(posedge fclk) if (fedge) begin
1621 if (f_rst) begin rd_pend <= {c}'d0; rd_out <= 1'b0; rd_owner <= {cb}'d0; end
1622 else begin
1623 for (rq = 0; rq < {c}; rq = rq + 1)
1624 if (c_mem_rd[rq] && !(rd_issue && rd_to == rq[{cbt}:0])) rd_pend_line[32*rq +: 32] <= c_mem_rd_line[32*rq +: 32];
1625 rd_pend <= rd_want & ~({{{{{cm}{{1'b0}}}}, rd_issue}} << rd_to);
1626 if (rd_issue) begin rd_out <= 1'b1; rd_owner <= rd_to; end
1627 else if (f_mem_rd_valid) rd_out <= 1'b0;
1628 end
1629 end
1630endmodule
1631"#,
1632 mdt = 4 * (super::stream::AW as usize + super::stream::LW as usize + 1) - 1,
1633 awt = super::stream::AW - 1,
1634 ct = c - 1,
1635 cm = c - 1,
1636 cbt = cb - 1,
1637 cbh = 28 + cb - 1,
1638 cj = c * j - 1,
1639 c32 = 32 * c - 1,
1640 c512 = 512 * c - 1,
1641 c64 = 64 * c - 1,
1642 cg = c * g - 1,
1643 cv = c * v - 1,
1644 ca = c * a - 1,
1645 e_ra = packed("e_ra"),
1646 e_wa = packed("e_wa"),
1647 pipes = channel_pipes(s),
1648 bridge = bridge.join(",\n "),
1649 )
1650}
1651
1652pub const PORT_PIPES: usize = 3;
1655
1656fn channel_pipes(s: &Streaming) -> String {
1659 use super::stream::{AW, LW};
1660 let (mdt, drt) = (4 * (AW + LW + 1) - 1, 4 * 8 * s.layout.line_bytes - 1);
1661 format!(
1662 r#" // Each channel's fast port through PORT_PIPES pipes each way at the engine's edge, so the path from the channel's bank
1663 // to the fast side crosses the die a pipe each part of the way.
1664 wire [3:0] p_md_valid, p_md_ready, p_mr_valid, p_mr_ready;
1665 wire [{mdt}:0] p_md_data;
1666 wire [{drt}:0] p_mr_data;
1667 genvar pipe_ch;
1668 generate for (pipe_ch = 0; pipe_ch < 4; pipe_ch = pipe_ch + 1) begin : port_pipe
1669 logos_pipe_chain #(.W({mdw}), .K({k})) md(.clk(fclk), .rst_n(!frst), .in_valid(p_md_valid[pipe_ch]), .in_ready(p_md_ready[pipe_ch]),
1670 .in_data(p_md_data[{mdw}*pipe_ch +: {mdw}]), .out_valid(sm_md_valid[pipe_ch]), .out_ready(sm_md_ready[pipe_ch]),
1671 .out_data(sm_md_data[{mdw}*pipe_ch +: {mdw}]));
1672 logos_pipe_chain #(.W({dw}), .K({k})) mr(.clk(fclk), .rst_n(!frst), .in_valid(sm_mr_valid[pipe_ch]), .in_ready(sm_mr_ready[pipe_ch]),
1673 .in_data(sm_mr_data[{dw}*pipe_ch +: {dw}]), .out_valid(p_mr_valid[pipe_ch]), .out_ready(p_mr_ready[pipe_ch]),
1674 .out_data(p_mr_data[{dw}*pipe_ch +: {dw}]));
1675{write_pipes} end endgenerate
1676"#,
1677 mdw = AW + LW + 1,
1678 dw = 8 * s.layout.line_bytes,
1679 k = PORT_PIPES,
1680 write_pipes = if s.recurrence {
1681 format!(
1682 " logos_pipe_chain #(.W({dw}), .K({k})) mw(.clk(fclk), .rst_n(!frst), .in_valid(p_mw_valid[pipe_ch]), .in_ready(p_mw_ready[pipe_ch]),\n \
1683 .in_data(p_mw_data[{dw}*pipe_ch +: {dw}]), .out_valid(sm_mw_valid[pipe_ch]), .out_ready(sm_mw_ready[pipe_ch]),\n \
1684 .out_data(sm_mw_data[{dw}*pipe_ch +: {dw}]));\n wire done_bit;\n \
1685 logos_pipe_chain #(.W(1), .K({k})) mdone(.clk(fclk), .rst_n(!frst), .in_valid(sm_mdone_valid[pipe_ch]), .in_ready(sm_mdone_ready[pipe_ch]),\n \
1686 .in_data(1'b0), .out_valid(p_mdone_valid[pipe_ch]), .out_ready(p_mdone_ready[pipe_ch]), .out_data(done_bit));\n",
1687 dw = 8 * s.layout.line_bytes,
1688 k = PORT_PIPES
1689 )
1690 } else {
1691 String::new()
1692 },
1693 )
1694}
1695
1696fn store_address_bits(p: &EngineParams) -> usize {
1698 if p.store_beats > 1 {
1699 (usize::BITS - (p.store_beats - 1).leading_zeros()) as usize
1700 } else {
1701 1
1702 }
1703}
1704
1705fn engine_ports() -> (&'static str, Vec<&'static str>) {
1707 let start = ENGINE_VERILOG.find(" (input clk").expect("the engine's ports");
1708 let end = ENGINE_VERILOG.find("@PORTS@);").expect("the engine's ports' end");
1709 let text = &ENGINE_VERILOG[start..end];
1710 let names = text.split(',').map(|d| d.split_whitespace().last().expect("a port's name")).collect();
1711 (text, names)
1712}
1713
1714
1715fn fast_verilog(p: &EngineParams, s: &Streaming) -> String {
1720 let (clk2_in, clk2_pass) = if s.set.pe_pairs { (" input clk2,", ".clk2(clk2), ") } else { ("", "") };
1722 use super::stream::{AW, LW};
1723 let sa = store_address_bits(p);
1724 let j = HybridJob::BITS;
1725 let set = &s.set;
1726 let nb_max = p.formats.iter().map(|&(ty, blocks)| (blocks * ty.block_len()).div_ceil(set.block_len(ty))).max().unwrap_or(1);
1728 let mdw = AW + LW + 1;
1729 let dw = 8 * s.layout.line_bytes;
1730 let text = format!(
1731 r#"// A hybrid engine's user-clock side: the streaming unit and the token-row store, the store the unit's while it owns it
1732// (own), the core's otherwise, read the cycle after the engine cycle's middle (cr) and written at its step (ce).
1733module logos_engine_fast
1734 (input clk,{clk2_in} input rst, input ce, input cr, input own, input go, input [{jt}:0] job,
1735 input [{st}:0] e_ra, input e_we, input [{st}:0] e_wa, input [511:0] e_wd, input [63:0] e_wm,
1736 output [511:0] rd, output reg busy_held,
1737 output [3:0] md_valid, input [3:0] md_ready, output [{mdt}:0] md_data,
1738 input [3:0] mr_valid, output [3:0] mr_ready, input [{drt}:0] mr_data);
1739 wire busy, u_we;
1740 wire start = go && cr;
1741 wire [{st}:0] u_ra, u_wa;
1742 wire [511:0] u_wd;
1743 wire [63:0] u_wm;
1744 wire [1023:0] runs = job[{runs_top}:{runs}];
1745 logos_streaming_matvec_{name} #(.N({n}), .T_MAX({t_max}), .NB_MAX({nb_max}), .SA({sa})) unit(.clk(clk), {clk2_pass}.rst(rst), .start(start),
1746 .greedy(job[{greedy}]), {fold}{fmt}.blocks(job[47:32]), .tokens(job[{tokens_top}:{tokens}]), {two}.rows(job[95:64]), .stripes(job[111:96]),
1747 .turns(job[135:128]), .from_base(job[{from_base}]), .from_beats(job[{from_beats}]), .to_base(job[{to_base}]),
1748 .to_beats(job[{to_beats}]), .busy(busy), .st_ra(u_ra), .st_rd(rd), .st_we(u_we), .st_wa(u_wa), .st_wd(u_wd),
1749 .st_wm(u_wm), .tr_count({count}), .tr_line({line}), .tr_lines({lines}), .tr_bytes({bytes}),
1750 .tr_last_lines({last_lines}), .tr_last_bytes({last_bytes}), .tr_row({row}), .tr_row_step({row_step}),
1751 .tensor(job[{tensor_top}:160]), .md_valid(md_valid), .md_ready(md_ready), .md_data(md_data), .mr_valid(mr_valid),
1752 .mr_ready(mr_ready), .mr_data(mr_data));
1753 always @(posedge clk)
1754 if (rst) busy_held <= 1'b0;
1755 else if (cr) busy_held <= busy || start;
1756 logos_bram_re_w512 #(.D({store}), .A({sa})) store(.clk(clk), .re(own || cr), .ra(own ? u_ra : e_ra), .rd(rd),
1757 .we(own ? u_we : ce && e_we), .wa(own ? u_wa : e_wa), .wd(own ? u_wd : e_wd), .wm(own ? u_wm : e_wm));
1758endmodule
1759"#,
1760 jt = j - 1,
1761 st = sa - 1,
1762 mdt = 4 * mdw - 1,
1763 drt = 4 * dw - 1,
1764 runs = HybridJob::RUNS,
1765 runs_top = HybridJob::RUNS + 1023,
1766 greedy = HybridJob::GREEDY,
1767 fold = if s.folds > 0 { format!(".fold(job[{}:{}]), ", HybridJob::FOLD + 1, HybridJob::FOLD) } else { String::new() },
1768 name = set.name,
1769 n = s.pairs(),
1770 t_max = p.rows,
1771 fmt = if set.formats.len() > 1 { ".fmt(job[3:0]), " } else { "" },
1772 two = if set.words == Words::Two { format!(".two(job[{}]), ", HybridJob::TWO) } else { String::new() },
1773 tokens = HybridJob::TOKENS,
1774 tokens_top = HybridJob::TOKENS + 7,
1775 from_base = format!("{}:{}", HybridJob::FROM + sa - 1, HybridJob::FROM),
1776 from_beats = format!("{}:{}", HybridJob::FROM + 47, HybridJob::FROM + 32),
1777 to_base = format!("{}:{}", HybridJob::TO + sa - 1, HybridJob::TO),
1778 to_beats = format!("{}:{}", HybridJob::TO + 47, HybridJob::TO + 32),
1779 count = runs_field(0, 32),
1780 line = runs_field(1, AW),
1781 lines = runs_field(2, LW),
1782 bytes = runs_field(3, super::stream::BYW),
1783 last_lines = runs_field(4, LW),
1784 last_bytes = runs_field(5, super::stream::BYW),
1785 row = runs_field(6, super::stream::ROWW),
1786 row_step = runs_field(7, super::stream::ROWW),
1787 tensor_top = 160 + super::stream::TW - 1,
1788 store = p.store_beats,
1789 );
1790 let text = if s.recurrence { fast_recurrence(&text, p, s) } else { text };
1791 let text = if s.vectors { fast_vectors(&text, p) } else { text };
1792 let text = if s.attention { fast_attention(&text, p) } else { text };
1793 let text = if s.recurrence_lanes > 1 { fast_lanes(&text, p, s) } else { text };
1794 if s.contexts > 1 {
1795 fast_contexts(&text, p, s)
1796 } else {
1797 text
1798 }
1799}
1800
1801fn fast_lanes(text: &str, p: &EngineParams, s: &Streaming) -> String {
1807 use super::stream::{AW, LW};
1808 let (hd, flush) = p.delta.expect("a recurrence unit runs a model's Gated-DeltaNet blocks");
1809 let (t_max, window) = recurrence_log(flush);
1810 let sa = store_address_bits(p);
1811 let (l, dw, mdw) = (s.recurrence_lanes, 8 * s.layout.line_bytes as usize, (AW + LW + 1) as usize);
1812 let lba = (usize::BITS - (s.log_lines.max(2) - 1).leading_zeros()) as usize;
1813 let ports = 4 * (l + 1);
1814 let lanes = format!(
1815 r#" // The recurrence's head lanes: each lane's rows and flushed rows through a fast port of its own, the lanes' ports and
1816 // the shared one onto the channels at once (port 0 the shared one, port 1 + k lane k's).
1817 wire [{lt}:0] l_rq_valid, l_rq_ready, l_rs_valid, l_wq_valid, l_wq_ready, l_writing, l_fault;
1818 wire [{lt32}:0] l_rq_line, l_wq_line;
1819 wire [{ltd}:0] l_rs_data, l_wq_data;
1820 wire p_fault, lanes_fault;
1821 wire [{pt}:0] c_md_valid, c_md_ready, c_mr_valid, c_mr_ready, c_mw_valid, c_mw_ready, c_mdone_valid, c_mdone_ready;
1822 wire [{pmd}:0] c_md_data;
1823 wire [{pdt}:0] c_mw_data;
1824 genvar lk;
1825 generate
1826 for (lk = 0; lk < {l}; lk = lk + 1) begin : lane_port
1827 logos_fast_port #(.NB(4), .AW({aw}), .LW({lw}), .DL({dw})) port(.clk(clk), .rst(rst), .space(map_space), .base(map_base),
1828 .granule_log2(map_granule_log2), .clear(g_start), .fault(l_fault[lk]), .rq_valid(l_rq_valid[lk]),
1829 .rq_ready(l_rq_ready[lk]), .rq_line(l_rq_line[32*lk +: 32]), .rs_valid(l_rs_valid[lk]),
1830 .rs_data(l_rs_data[{dw}*lk +: {dw}]), .wq_valid(l_wq_valid[lk]), .wq_ready(l_wq_ready[lk]),
1831 .wq_line(l_wq_line[32*lk +: 32]), .wq_data(l_wq_data[{dw}*lk +: {dw}]), .writing(l_writing[lk]),
1832 .md_valid(c_md_valid[4*(lk+1) +: 4]), .md_ready(c_md_ready[4*(lk+1) +: 4]),
1833 .md_data(c_md_data[{mdw4}*(lk+1) +: {mdw4}]), .mr_valid(c_mr_valid[4*(lk+1) +: 4]),
1834 .mr_ready(c_mr_ready[4*(lk+1) +: 4]), .mr_data(mr_data), .mw_valid(c_mw_valid[4*(lk+1) +: 4]),
1835 .mw_ready(c_mw_ready[4*(lk+1) +: 4]), .mw_data(c_mw_data[{dw4}*(lk+1) +: {dw4}]),
1836 .mdone_valid(c_mdone_valid[4*(lk+1) +: 4]), .mdone_ready(c_mdone_ready[4*(lk+1) +: 4]));
1837 end
1838 endgenerate
1839 // The ports straight onto the multiplexer, which holds each port's descriptors and write lines in registers of its own.
1840 logos_channel_mux #(.P({np}), .NB(4), .AW({aw}), .LW({lw}), .DL({dw})) mux(.clk(clk), .rst(rst), .pd_valid(c_md_valid),
1841 .pd_ready(c_md_ready), .pd_data(c_md_data), .pr_valid(c_mr_valid), .pr_ready(c_mr_ready), .pw_valid(c_mw_valid),
1842 .pw_ready(c_mw_ready), .pw_data(c_mw_data), .pdone_valid(c_mdone_valid), .pdone_ready(c_mdone_ready),
1843 .md_valid(p_md_valid), .md_ready(md_ready), .md_data(p_md_data), .mr_valid(mr_valid), .mr_ready(p_mr_ready),
1844 .mw_valid(mw_valid), .mw_ready(mw_ready), .mw_data(mw_data), .mdone_valid(mdone_valid), .mdone_ready(mdone_ready));
1845 assign gdn_fault = p_fault || l_fault != {l}'d0 || lanes_fault;
1846"#,
1847 lt = l - 1,
1848 lt32 = 32 * l - 1,
1849 ltd = dw * l - 1,
1850 pt = ports - 1,
1851 pmd = ports * mdw - 1,
1852 pdt = ports * dw - 1,
1853 mdw4 = 4 * mdw,
1854 dw4 = 4 * dw,
1855 np = l + 1,
1856 aw = AW,
1857 lw = LW,
1858 );
1859 let w = s.recurrence_width;
1860 let fl = s.recurrence_flushes;
1861 assert!(
1862 hd % (fl * w * FADD_LATENCY) == 0,
1863 "a {hd}-wide head's rows split over {fl} flushes, each a whole number of the flush's groups of {} rows",
1864 w * FADD_LATENCY
1865 );
1866 if let Some(window) = window {
1867 assert!(fl == 1, "a trailing flush replays its shares on one flush unit a lane, not {fl}");
1868 assert!(
1869 hd % window == 0 && (hd / window) % (w * FADD_LATENCY) == 0,
1870 "a trailing flush of {window} tokens replays a {hd}-wide head's rows in shares of a whole number of the read-out's groups of {} rows",
1871 w * FADD_LATENCY
1872 );
1873 }
1874 let text = replaced(
1875 text,
1876 &[
1877 (
1878 &format!(" logos_gdn_side #(.HD({hd}), .TMAX({t_max}), .SA({sa}), .W({w})) side("),
1879 &format!(
1880 "{lanes} logos_gdn_lanes #(.HD({hd}), .TMAX({t_max}), .SA({sa}), {credit}.W({w}), .L({l}), .LBA({lba}), .LOG_LINES({ll}){flushes}) side(",
1881 ll = s.log_lines,
1882 credit = if s.recurrence_credit != super::gdn::LANE_CREDIT { format!(".OUTSTANDING({}), ", s.recurrence_credit) } else { String::new() },
1883 flushes = if fl > 1 { format!(", .FLUSHES({fl})") } else { String::new() }
1884 ),
1885 ),
1886 (
1887 "\n .written(!writing));\n",
1888 &format!(
1889 "\n .written(!writing), .fault(lanes_fault), .l_rq_valid(l_rq_valid), .l_rq_ready(l_rq_ready),\n \
1890 .l_rq_line(l_rq_line), .l_rs_valid(l_rs_valid), .l_rs_data(l_rs_data), .l_wq_valid(l_wq_valid),\n \
1891 .l_wq_ready(l_wq_ready), .l_wq_line(l_wq_line), .l_wq_data(l_wq_data), .l_written(~l_writing),\n \
1892 .rows(gdn_job[{}:{}]), .row_valid(g_row), .row_ready(g_take));\n",
1893 GdnJob::ROWS + 15,
1894 GdnJob::ROWS
1895 ),
1896 ),
1897 (".fault(gdn_fault), .rq_valid(", ".fault(p_fault), .rq_valid("),
1898 (
1899 " wire g_row, g_take, g_side_busy;\n",
1900 " wire g_row, g_take;\n",
1901 ),
1902 (
1903 " reg g_took;\n always @(posedge clk) g_took <= !rst && g_take;\n assign g_take = g_row && !g_side_busy && !g_took && !writing;\n assign g_busy = g_side_busy || g_row;\n",
1904 "",
1905 ),
1906 (".start(g_take), .busy(g_side_busy),", ".start(g_start), .busy(g_busy),"),
1907 (
1908 ".md_valid(p_md_valid), .md_ready(md_ready), .md_data(p_md_data),\n \
1909 .mr_valid(mr_valid), .mr_ready(p_mr_ready), .mr_data(mr_data), .mw_valid(mw_valid), .mw_ready(mw_ready),\n \
1910 .mw_data(mw_data), .mdone_valid(mdone_valid), .mdone_ready(mdone_ready));\n",
1911 &format!(
1912 ".md_valid(c_md_valid[3:0]), .md_ready(c_md_ready[3:0]),\n \
1913 .md_data(c_md_data[{m}:0]), .mr_valid(c_mr_valid[3:0]), .mr_ready(c_mr_ready[3:0]), .mr_data(mr_data),\n \
1914 .mw_valid(c_mw_valid[3:0]), .mw_ready(c_mw_ready[3:0]), .mw_data(c_mw_data[{d}:0]), .mdone_valid(c_mdone_valid[3:0]),\n \
1915 .mdone_ready(c_mdone_ready[3:0]));\n",
1916 m = 4 * mdw - 1,
1917 d = 4 * dw - 1
1918 ),
1919 ),
1920 ],
1921 );
1922 if !s.delta_norm {
1923 return text;
1924 }
1925 let field = |at: usize, bits: usize| format!("gdn_job[{}:{at}]", at + bits - 1);
1927 replaced(
1928 &text,
1929 &[
1930 (
1931 &format!(" wire [{st}:0] g_q, g_out, g_k, g_v, g_ld, g_gt;\n", st = sa - 1),
1932 &format!(" wire [{st}:0] g_q, g_out, g_k, g_v, g_ld, g_gt, g_z;\n", st = sa - 1),
1933 ),
1934 (".ld_beat(g_ld), .gt_beat(g_gt));\n", &format!(".ld_beat(g_ld), .gt_beat(g_gt), .z_slot({}), .z_beat(g_z));\n", field(GdnJob::Z, 5))),
1935 (
1936 ".gt_beat(g_gt), .st_ra(g_ra),",
1937 &format!(
1938 ".gt_beat(g_gt), .norm(gdn_job[{}]), .eps({}), .z_beat(g_z),\n .w_line({}), .st_ra(g_ra),",
1939 GdnJob::NORM,
1940 field(GdnJob::EPS, 32),
1941 field(GdnJob::W_LINE, 32)
1942 ),
1943 ),
1944 ],
1945 )
1946}
1947
1948fn fast_vectors(text: &str, p: &EngineParams) -> String {
1953 let sa = store_address_bits(p);
1954 let unit = format!(
1955 r#" // The vector unit's side: a vector job on the fast side's vector unit, started the cycle after the middle of the
1956 // engine cycle the core hands it over in (vec_go); the store and the recurrence unit's port its from then to the next
1957 // matvec or recurrence job's start; its tables written at the core's step, as the core's are.
1958 (* maxfan = 256 *) reg vec;
1959 always @(posedge clk)
1960 if (rst) vec <= 1'b0;
1961 else if (start || g_start) vec <= 1'b0;
1962 else if (v_start) vec <= 1'b1;
1963 wire v_busy, v_we, v_rq_valid, v_wq_valid;
1964 wire [{st}:0] v_ra, v_wa;
1965 wire [511:0] v_wd, v_wq_data;
1966 wire [63:0] v_wm;
1967 wire [31:0] v_rq_line, v_wq_line;
1968 logos_vector_side #(.ROW_MAX({rows}), .VROWS({vrows}), .PROWS({prows}), .FU({fu}), .JOB_WORDS({jobs}), .TENSORS({tensors}),
1969 .LAYERS({layers}), .ROPES({ropes}), .SA({sa}), .FM_ROWS({fm_rows}), .PASSES({passes}), .DEPTH({depth})) vside(.clk(clk), .rst(rst), .start(v_start), .job_pc(vec_job[{pc_top}:{pc}]),
1970 .rows(vec_job[{rows_top}:{rows_at}]), .state_f16(vec_job[{f16}]), .busy(v_busy), .port_fault(gdn_fault), .fault(vec_fault),
1971 .host_we(ce && host_wr), .host_table(host_table), .host_addr(host_addr), .host_wdata(host_wdata), .st_ra(v_ra), .st_rd(rd),
1972 .st_we(v_we), .st_wa(v_wa), .st_wd(v_wd), .st_wm(v_wm), .rq_valid(v_rq_valid), .rq_ready(vec && p_rq_ready),
1973 .rq_line(v_rq_line), .rs_valid(vec && p_rs_valid), .rs_data(rs_data), .wq_valid(v_wq_valid), .wq_ready(vec && p_wq_ready),
1974 .wq_line(v_wq_line), .wq_data(v_wq_data), .written(!writing));
1975 assign rq_ready = !vec && p_rq_ready;
1976 assign rs_valid = !vec && p_rs_valid;
1977 assign wq_ready = !vec && p_wq_ready;
1978"#,
1979 st = sa - 1,
1980 rows = p.rows,
1981 vrows = p.side_rows,
1982 prows = p.pair_rows.min(p.side_rows),
1983 fu = p.function_units,
1984 jobs = p.job_words,
1985 tensors = p.tensors.max(1),
1986 layers = p.layers.max(1),
1987 ropes = p.ropes.max(1),
1988 fm_rows = p.streaming.as_ref().map_or(super::vector_side::FM_ROWS, |s| s.float_rows),
1989 passes = p.streaming.as_ref().map_or(1, |s| s.vector_passes),
1990 depth = p.streaming.as_ref().map_or(1, |s| s.vector_depth),
1991 pc = VectorSideJob::PC,
1992 pc_top = VectorSideJob::PC + 31,
1993 rows_at = VectorSideJob::ROWS,
1994 rows_top = VectorSideJob::ROWS + 15,
1995 f16 = VectorSideJob::F16,
1996 );
1997 replaced(
1998 text,
1999 &[
2000 (
2001 "// A hybrid engine's user-clock side: the streaming unit, the recurrence unit and the token-row store, the store the\n\
2002 // units' while they own it (own), the core's otherwise, read the cycle after the engine cycle's middle (cr) and\n\
2003 // written at its step (ce).\n",
2004 "// A hybrid engine's user-clock side: the streaming unit, the recurrence unit, the vector unit's side and the token-row\n\
2005 // store, the store the units' while they own it (own), the core's otherwise, read the cycle after the engine cycle's\n\
2006 // middle (cr) and written at its step (ce).\n",
2007 ),
2008 (
2009 " input [31:0] map_space,",
2010 &format!(
2011 " input vec_go, input [{v}:0] vec_job, output vec_fault,\n input [31:0] map_space,",
2012 v = VectorSideJob::BITS - 1
2013 ),
2014 ),
2015 (" wire g_start = gdn_go && cr;\n", " wire g_start = gdn_go && cr;\n wire v_start = vec_go && cr;\n"),
2016 (" else if (start) gdn <= 1'b0;\n", " else if (start || v_start) gdn <= 1'b0;\n"),
2017 (" wire g_busy, g_we, rq_valid, rq_ready, rs_valid, wq_valid, wq_ready, writing;\n", " wire g_busy, g_we, rq_valid, rq_ready, rs_valid, wq_valid, wq_ready, writing, p_rq_ready, p_rs_valid, p_wq_ready;\n"),
2018 (
2019 ".clear(g_start), .fault(gdn_fault), .rq_valid(rq_valid), .rq_ready(rq_ready),\n \
2020 .rq_line(rq_line), .rs_valid(rs_valid), .rs_data(rs_data), .wq_valid(wq_valid), .wq_ready(wq_ready), .wq_line(wq_line),\n \
2021 .wq_data(wq_data), .writing(writing),",
2022 ".clear(g_start || v_start), .fault(gdn_fault), .rq_valid(vec ? v_rq_valid : rq_valid),\n \
2023 .rq_ready(p_rq_ready), .rq_line(vec ? v_rq_line : rq_line), .rs_valid(p_rs_valid), .rs_data(rs_data),\n \
2024 .wq_valid(vec ? v_wq_valid : wq_valid), .wq_ready(p_wq_ready), .wq_line(vec ? v_wq_line : wq_line),\n \
2025 .wq_data(vec ? v_wq_data : wq_data), .writing(writing),",
2026 ),
2027 (" assign md_valid = gdn ? p_md_valid : u_md_valid;\n", " assign md_valid = (gdn || vec) ? p_md_valid : u_md_valid;\n"),
2028 (" assign md_data = gdn ? p_md_data : u_md_data;\n", " assign md_data = (gdn || vec) ? p_md_data : u_md_data;\n"),
2029 (" assign mr_ready = gdn ? p_mr_ready : u_mr_ready;\n", " assign mr_ready = (gdn || vec) ? p_mr_ready : u_mr_ready;\n"),
2030 (" logos_fast_port #(", &format!("{unit} logos_fast_port #(")),
2031 (
2032 " else if (cr) busy_held <= busy || start || g_busy || writing || g_start;\n",
2033 " else if (cr) busy_held <= busy || start || g_busy || writing || g_start || v_busy || v_start;\n",
2034 ),
2035 (
2036 ".re(own || cr), .ra(own ? (gdn ? g_ra : u_ra) : e_ra), .rd(rd),\n \
2037 .we(own ? (gdn ? g_we : u_we) : ce && e_we), .wa(own ? (gdn ? g_wa : u_wa) : e_wa),\n \
2038 .wd(own ? (gdn ? g_wd : u_wd) : e_wd), .wm(own ? (gdn ? {64{1'b1}} : u_wm) : e_wm));\n",
2039 ".re(own || cr), .ra(own ? (vec ? v_ra : gdn ? g_ra : u_ra) : e_ra), .rd(rd),\n \
2040 .we(own ? (vec ? v_we : gdn ? g_we : u_we) : ce && e_we), .wa(own ? (vec ? v_wa : gdn ? g_wa : u_wa) : e_wa),\n \
2041 .wd(own ? (vec ? v_wd : gdn ? g_wd : u_wd) : e_wd), .wm(own ? (vec ? v_wm : gdn ? {64{1'b1}} : u_wm) : e_wm));\n",
2042 ),
2043 ],
2044 )
2045}
2046
2047fn fast_attention(text: &str, p: &EngineParams) -> String {
2052 let sa = store_address_bits(p);
2053 let (hd, vd, ctx, _) = p.attend.expect("an attention side attends a model's attention blocks");
2054 let field = |at: usize, bits: usize| format!("att_job[{}:{at}]", at + bits - 1);
2055 let unit = format!(
2056 r#" wire a_busy, a_we, a_rq_valid;
2057 wire [{st}:0] a_ra, a_wa;
2058 wire [511:0] a_wd;
2059 wire [31:0] a_rq_line;
2060 logos_attend_side #(.HD({hd}), .VD({vd}), .P_MAX({ctx}), .SA({sa})) aside(.clk(clk), .rst(rst), .start(a_start), .busy(a_busy),
2061 .region({region}), .keys({keys}), .values({values}), .kscales({kscales}), .vscales({vscales}), .kv_heads({kv}),
2062 .heads({heads}), .positions({positions}), .scale({scale}), .q_beat({q}), .out_beat({o}), .st_ra(a_ra), .st_rd(rd),
2063 .st_we(a_we), .st_wa(a_wa), .st_wd(a_wd), .rq_valid(a_rq_valid), .rq_ready(att && p_rq_ready), .rq_line(a_rq_line),
2064 .rs_valid(att && p_rs_valid), .rs_data(rs_data));
2065"#,
2066 st = sa - 1,
2067 region = field(AttendJob::REGION, 32),
2068 keys = field(AttendJob::KEYS, 32),
2069 values = field(AttendJob::VALUES, 32),
2070 kscales = field(AttendJob::KEY_SCALES, 32),
2071 vscales = field(AttendJob::VALUE_SCALES, 32),
2072 kv = field(AttendJob::KV_HEADS, 32),
2073 heads = field(AttendJob::HEADS, 16),
2074 positions = field(AttendJob::POSITIONS, 32),
2075 scale = field(AttendJob::SCALE, 32),
2076 q = field(AttendJob::QUERY, sa),
2077 o = field(AttendJob::OUTPUT, sa),
2078 );
2079 replaced(
2080 text,
2081 &[
2082 (
2083 "// A hybrid engine's user-clock side: the streaming unit, the recurrence unit, the vector unit's side and the token-row\n\
2084 // store, the store the units' while they own it (own), the core's otherwise, read the cycle after the engine cycle's\n\
2085 // middle (cr) and written at its step (ce).\n",
2086 "// A hybrid engine's user-clock side: the streaming unit, the recurrence unit, the vector unit's side, the attention side\n\
2087 // and the token-row store, the store the units' while they own it (own), the core's otherwise, read the cycle after\n\
2088 // the engine cycle's middle (cr) and written at its step (ce).\n",
2089 ),
2090 (" input [31:0] map_space,", &format!(" input att_go, input [{}:0] att_job,\n input [31:0] map_space,", AttendJob::BITS - 1)),
2091 (
2092 " wire v_start = vec_go && cr;\n",
2093 " wire v_start = vec_go && cr;\n wire a_start = att_go && cr;\n \
2094 // The attention side's: a token-row of an attention job, from the cycle after the middle of the engine cycle the\n \
2095 // core hands it over in (att_go) to the next unit's start.\n \
2096 (* maxfan = 256 *) reg att;\n \
2097 always @(posedge clk)\n \
2098 if (rst) att <= 1'b0;\n \
2099 else if (start || g_start || v_start) att <= 1'b0;\n \
2100 else if (a_start) att <= 1'b1;\n",
2101 ),
2102 (" else if (start || v_start) gdn <= 1'b0;\n", " else if (start || v_start || a_start) gdn <= 1'b0;\n"),
2103 (" else if (start || g_start) vec <= 1'b0;\n", " else if (start || g_start || a_start) vec <= 1'b0;\n"),
2104 (" assign rq_ready = !vec && p_rq_ready;\n", " assign rq_ready = !vec && !att && p_rq_ready;\n"),
2105 (" assign rs_valid = !vec && p_rs_valid;\n", " assign rs_valid = !vec && !att && p_rs_valid;\n"),
2106 (" assign wq_ready = !vec && p_wq_ready;\n", " assign wq_ready = !vec && !att && p_wq_ready;\n"),
2107 (
2108 ".clear(g_start || v_start), .fault(gdn_fault), .rq_valid(vec ? v_rq_valid : rq_valid),\n \
2109 .rq_ready(p_rq_ready), .rq_line(vec ? v_rq_line : rq_line), .rs_valid(p_rs_valid), .rs_data(rs_data),\n \
2110 .wq_valid(vec ? v_wq_valid : wq_valid),",
2111 ".clear(g_start || v_start || a_start), .fault(gdn_fault), .rq_valid(att ? a_rq_valid : vec ? v_rq_valid : rq_valid),\n \
2112 .rq_ready(p_rq_ready), .rq_line(att ? a_rq_line : vec ? v_rq_line : rq_line), .rs_valid(p_rs_valid), .rs_data(rs_data),\n \
2113 .wq_valid(!att && (vec ? v_wq_valid : wq_valid)),",
2114 ),
2115 (" assign md_valid = (gdn || vec) ? p_md_valid : u_md_valid;\n", " assign md_valid = (gdn || vec || att) ? p_md_valid : u_md_valid;\n"),
2116 (" assign md_data = (gdn || vec) ? p_md_data : u_md_data;\n", " assign md_data = (gdn || vec || att) ? p_md_data : u_md_data;\n"),
2117 (" assign mr_ready = (gdn || vec) ? p_mr_ready : u_mr_ready;\n", " assign mr_ready = (gdn || vec || att) ? p_mr_ready : u_mr_ready;\n"),
2118 (" logos_fast_port #(", &format!("{unit} logos_fast_port #(")),
2119 (
2120 " else if (cr) busy_held <= busy || start || g_busy || writing || g_start || v_busy || v_start;\n",
2121 " else if (cr) busy_held <= busy || start || g_busy || writing || g_start || v_busy || v_start || a_busy || a_start;\n",
2122 ),
2123 (
2124 ".re(own || cr), .ra(own ? (vec ? v_ra : gdn ? g_ra : u_ra) : e_ra), .rd(rd),\n \
2125 .we(own ? (vec ? v_we : gdn ? g_we : u_we) : ce && e_we), .wa(own ? (vec ? v_wa : gdn ? g_wa : u_wa) : e_wa),\n \
2126 .wd(own ? (vec ? v_wd : gdn ? g_wd : u_wd) : e_wd), .wm(own ? (vec ? v_wm : gdn ? {64{1'b1}} : u_wm) : e_wm));\n",
2127 ".re(own || cr), .ra(own ? (att ? a_ra : vec ? v_ra : gdn ? g_ra : u_ra) : e_ra), .rd(rd),\n \
2128 .we(own ? (att ? a_we : vec ? v_we : gdn ? g_we : u_we) : ce && e_we),\n \
2129 .wa(own ? (att ? a_wa : vec ? v_wa : gdn ? g_wa : u_wa) : e_wa),\n \
2130 .wd(own ? (att ? a_wd : vec ? v_wd : gdn ? g_wd : u_wd) : e_wd),\n \
2131 .wm(own ? (att ? {64{1'b1}} : vec ? v_wm : gdn ? {64{1'b1}} : u_wm) : e_wm));\n",
2132 ),
2133 ],
2134 )
2135}
2136
2137fn fast_recurrence(text: &str, p: &EngineParams, s: &Streaming) -> String {
2143 use super::stream::{AW, LW};
2144 let (hd, flush) = p.delta.expect("a recurrence unit runs a model's Gated-DeltaNet blocks");
2145 let (t_max, window) = recurrence_log(flush);
2146 assert!(window.is_none() || s.recurrence_lanes > 1, "a trailing flush runs on the recurrence's head lanes alone");
2147 let sa = store_address_bits(p);
2148 let (mdt, dw) = (4 * (AW + LW + 1) - 1, 8 * s.layout.line_bytes);
2149 let field = |at: usize, bits: usize| format!("gdn_job[{}:{at}]", at + bits - 1);
2150 let unit = format!(
2151 r#" // The recurrence unit: a lazily flushed memory's token-rows on the channels through the fast port, the job started the
2152 // cycle after the middle of the engine cycle the core gives it the job in (gdn_go); the store and the channels the
2153 // unit's from then to the next matvec job's start. The job's rows given on in order (logos_gdn_rows), each laid from
2154 // the rows and slots tables, the side taking the next once it is idle and the last row's writes are on the card (the
2155 // next row may be the same sequence's); the unit busy while a row is left.
2156 wire g_start = gdn_go && cr;
2157 reg gdn;
2158 always @(posedge clk)
2159 if (rst) gdn <= 1'b0;
2160 else if (start) gdn <= 1'b0;
2161 else if (g_start) gdn <= 1'b1;
2162 wire g_busy, g_we, rq_valid, rq_ready, rs_valid, wq_valid, wq_ready, writing;
2163 wire [{st}:0] g_ra, g_wa;
2164 wire [511:0] g_wd;
2165 wire [{dt}:0] rs_data, wq_data;
2166 wire [31:0] rq_line, wq_line;
2167 wire [3:0] p_md_valid, p_mr_ready;
2168 wire [{mdt}:0] p_md_data;
2169 wire g_row, g_take, g_side_busy;
2170 wire [31:0] g_region;
2171 wire [{st}:0] g_q, g_out, g_k, g_v, g_ld, g_gt;
2172 logos_gdn_rows #(.SA({sa}), .ROW_MAX({row_max})) rows_of(.clk(clk), .rst(rst), .start(g_start), .rows({rows}),
2173 .q_slot({q_slot}), .out_slot({out_slot}), .k_slot({k_slot}), .v_slot({v_slot}), .v_at({v_at}), .ld_slot({ld_slot}),
2174 .gt_slot({gt_slot}), .host_we(ce && host_wr), .host_table(host_table), .host_addr(host_addr), .host_wdata(host_wdata),
2175 .row_valid(g_row), .row_ready(g_take), .region(g_region), .q_beat(g_q), .out_beat(g_out), .k_beat(g_k), .v_beat(g_v),
2176 .ld_beat(g_ld), .gt_beat(g_gt));
2177 reg g_took;
2178 always @(posedge clk) g_took <= !rst && g_take;
2179 assign g_take = g_row && !g_side_busy && !g_took && !writing;
2180 assign g_busy = g_side_busy || g_row;
2181 logos_gdn_side #(.HD({hd}), .TMAX({t_max}), .SA({sa}), .W({w})) side(.clk(clk), .rst(rst), .start(g_take), .busy(g_side_busy),
2182 .region(g_region), .mem_off({memory}), .log_off({log}), .entry({entry}), .nk({nk}), .nv({nv}), .fval({fval}),
2183 .fdec({fdec}), .fgate({fgate}), .scale({scale}), .tokens({tokens}), .f16(gdn_job[{f16}]), .q_beat(g_q),
2184 .out_beat(g_out), .k_beat(g_k), .v_beat(g_v), .ld_beat(g_ld), .gt_beat(g_gt), .st_ra(g_ra),
2185 .st_rd(rd), .st_we(g_we), .st_wa(g_wa), .st_wd(g_wd), .rq_valid(rq_valid), .rq_ready(rq_ready), .rq_line(rq_line),
2186 .rs_valid(rs_valid), .rs_data(rs_data), .wq_valid(wq_valid), .wq_ready(wq_ready), .wq_line(wq_line), .wq_data(wq_data),
2187 .written(!writing));
2188 logos_fast_port #(.NB(4), .AW({aw}), .LW({lw}), .DL({dw})) port(.clk(clk), .rst(rst), .space(map_space), .base(map_base),
2189 .granule_log2(map_granule_log2), .clear(g_start), .fault(gdn_fault), .rq_valid(rq_valid), .rq_ready(rq_ready),
2190 .rq_line(rq_line), .rs_valid(rs_valid), .rs_data(rs_data), .wq_valid(wq_valid), .wq_ready(wq_ready), .wq_line(wq_line),
2191 .wq_data(wq_data), .writing(writing), .md_valid(p_md_valid), .md_ready(md_ready), .md_data(p_md_data),
2192 .mr_valid(mr_valid), .mr_ready(p_mr_ready), .mr_data(mr_data), .mw_valid(mw_valid), .mw_ready(mw_ready),
2193 .mw_data(mw_data), .mdone_valid(mdone_valid), .mdone_ready(mdone_ready));
2194 assign md_valid = gdn ? p_md_valid : u_md_valid;
2195 assign md_data = gdn ? p_md_data : u_md_data;
2196 assign mr_ready = gdn ? p_mr_ready : u_mr_ready;
2197"#,
2198 st = sa - 1,
2199 dt = dw - 1,
2200 w = s.recurrence_width,
2201 aw = AW,
2202 lw = LW,
2203 rows = field(GdnJob::ROWS, 16),
2204 row_max = p.rows,
2205 memory = field(GdnJob::MEMORY, 32),
2206 log = field(GdnJob::LOG, 32),
2207 entry = field(GdnJob::ENTRY, 32),
2208 nk = field(GdnJob::KEY_HEADS, 32),
2209 nv = field(GdnJob::VALUE_HEADS, 32),
2210 fval = field(GdnJob::VALUES, 32),
2211 fdec = field(GdnJob::DECAYS, 32),
2212 fgate = field(GdnJob::GATES, 32),
2213 scale = field(GdnJob::SCALE, 32),
2214 q_slot = field(GdnJob::QUERY, 5),
2215 out_slot = field(GdnJob::OUTPUT, 5),
2216 k_slot = field(GdnJob::KEY, 5),
2217 v_slot = field(GdnJob::VALUE, 5),
2218 v_at = field(GdnJob::VALUE_AT, 20),
2219 ld_slot = field(GdnJob::LOG_DECAY, 5),
2220 gt_slot = field(GdnJob::GATE, 5),
2221 tokens = field(GdnJob::TOKENS, 8),
2222 f16 = GdnJob::F16,
2223 );
2224 replaced(
2225 text,
2226 &[
2227 (
2228 "// A hybrid engine's user-clock side: the streaming unit and the token-row store, the store the unit's while it owns it\n\
2229 // (own), the core's otherwise, read the cycle after the engine cycle's middle (cr) and written at its step (ce).\n",
2230 "// A hybrid engine's user-clock side: the streaming unit, the recurrence unit and the token-row store, the store the\n\
2231 // units' while they own it (own), the core's otherwise, read the cycle after the engine cycle's middle (cr) and\n\
2232 // written at its step (ce).\n",
2233 ),
2234 (
2235 &format!(" input [3:0] mr_valid, output [3:0] mr_ready, input [{}:0] mr_data);\n", 4 * dw - 1),
2236 &format!(
2237 " input [3:0] mr_valid, output [3:0] mr_ready, input [{d}:0] mr_data,\n \
2238 output [3:0] mw_valid, input [3:0] mw_ready, output [{d}:0] mw_data, input [3:0] mdone_valid, output [3:0] mdone_ready,\n \
2239 input gdn_go, input [{g}:0] gdn_job, output gdn_fault,\n \
2240 input host_wr, input [2:0] host_table, input [31:0] host_addr, input [511:0] host_wdata,\n \
2241 input [31:0] map_space, input [{a}:0] map_base, input [4:0] map_granule_log2);\n",
2242 d = 4 * dw - 1,
2243 g = GdnJob::bits(s) - 1,
2244 a = AW - 1
2245 ),
2246 ),
2247 (
2248 " wire busy, u_we;\n",
2249 &format!(" wire busy, u_we;\n wire [3:0] u_md_valid, u_mr_ready;\n wire [{mdt}:0] u_md_data;\n"),
2250 ),
2251 (
2252 ".md_valid(md_valid), .md_ready(md_ready), .md_data(md_data), .mr_valid(mr_valid),\n .mr_ready(mr_ready), .mr_data(mr_data));\n",
2253 &format!(
2254 ".md_valid(u_md_valid), .md_ready(md_ready), .md_data(u_md_data), .mr_valid(mr_valid),\n \
2255 .mr_ready(u_mr_ready), .mr_data(mr_data));\n{unit}"
2256 ),
2257 ),
2258 (" else if (cr) busy_held <= busy || start;\n", " else if (cr) busy_held <= busy || start || g_busy || writing || g_start;\n"),
2259 (
2260 ".re(own || cr), .ra(own ? u_ra : e_ra), .rd(rd),\n .we(own ? u_we : ce && e_we), .wa(own ? u_wa : e_wa), .wd(own ? u_wd : e_wd), .wm(own ? u_wm : e_wm));\n",
2261 ".re(own || cr), .ra(own ? (gdn ? g_ra : u_ra) : e_ra), .rd(rd),\n \
2262 .we(own ? (gdn ? g_we : u_we) : ce && e_we), .wa(own ? (gdn ? g_wa : u_wa) : e_wa),\n \
2263 .wd(own ? (gdn ? g_wd : u_wd) : e_wd), .wm(own ? (gdn ? {64{1'b1}} : u_wm) : e_wm));\n",
2264 ),
2265 ],
2266 )
2267}
2268
2269fn fast_contexts(text: &str, p: &EngineParams, s: &Streaming) -> String {
2285 let clk2_in = if s.set.pe_pairs { " input clk2," } else { "" };
2287 use super::stream::{AW, LW};
2288 let c = s.contexts;
2289 let cb = if c > 1 { (usize::BITS - (c - 1).leading_zeros()) as usize } else { 1 };
2290 let sa = store_address_bits(p);
2291 let (j, g, v, a) = (HybridJob::BITS, GdnJob::bits(s), VectorSideJob::BITS, AttendJob::BITS);
2292 let (mdw, dw) = (AW as usize + LW as usize + 1, 8 * s.layout.line_bytes as usize);
2293 let pick = |words: &str, width: usize, at: &str| {
2296 (0..c - 1).rev().fold(format!("{words}[{} +: {width}]", width * (c - 1)), |rest, k| format!("({at} == {cb}'d{k}) ? {words}[{} +: {width}] : {rest}", width * k))
2297 };
2298 let head = text.find("module logos_engine_fast\n").expect("the fast side's module");
2299 let body_at = head + text[head..].find(");\n").expect("the fast side's ports") + 3;
2300 let body = replaced(
2301 &text[body_at..],
2302 &[
2303 (" wire start = go && cr;\n", "@CONTEXT_WIRES@"),
2304 (" wire g_start = gdn_go && cr;\n", ""),
2305 (" wire v_start = vec_go && cr;\n", ""),
2306 (" wire a_start = att_go && cr;\n", ""),
2307 (" else if (start || g_start || v_start) att <= 1'b0;\n", " else if (g_start || v_start) att <= 1'b0;\n"),
2308 (" else if (start || v_start || a_start) gdn <= 1'b0;\n", " else if (v_start || a_start) gdn <= 1'b0;\n"),
2309 (" else if (start || g_start || a_start) vec <= 1'b0;\n", " else if (g_start || a_start) vec <= 1'b0;\n"),
2310 (" assign md_valid = (gdn || vec || att) ? p_md_valid : u_md_valid;\n", ""),
2311 (" assign md_data = (gdn || vec || att) ? p_md_data : u_md_data;\n", ""),
2312 (" assign mr_ready = (gdn || vec || att) ? p_mr_ready : u_mr_ready;\n", ""),
2313 (
2314 " always @(posedge clk)\n if (rst) busy_held <= 1'b0;\n \
2315 else if (cr) busy_held <= busy || start || g_busy || writing || g_start || v_busy || v_start || a_busy || a_start;\n",
2316 "",
2317 ),
2318 ],
2319 );
2320 let body = spliced(&body, " logos_bram_re_w512 #(.D(", "endmodule\n", "@CONTEXT_GRANTS@endmodule\n");
2321 let mut body = body;
2322 for name in ["md_valid", "md_ready", "md_data", "mr_valid", "mr_ready", "mr_data", "mw_valid", "mw_ready", "mw_data", "mdone_valid", "mdone_ready"] {
2323 body = renamed(&body, name, &format!("x_{name}"));
2324 }
2325 for (from, to) in [("job", "mjob"), ("gdn_job", "gjob"), ("vec_job", "vjob"), ("att_job", "ajob"), ("gdn_fault", "g_fault"), ("vec_fault", "v_fault"), ("rd", "w_rd")] {
2326 body = renamed(&body, from, to);
2327 }
2328 let unit = body.find(" unit(.clk(clk)").expect("the streaming unit's instance");
2329 let unit_end = unit + body[unit..].find(");\n").expect("its end");
2330 let instance = replaced(
2331 &body[unit..unit_end],
2332 &[
2333 (".md_ready(x_md_ready)", ".md_ready(u_md_ready)"),
2334 (".mr_valid(x_mr_valid)", ".mr_valid(u_mr_valid)"),
2335 (".mr_data(x_mr_data)", ".mr_data(u_mr_data)"),
2336 (".st_rd(w_rd)", ".st_rd(u_rd)"),
2337 ],
2338 );
2339 let body = format!("{}{instance}{}", &body[..unit], &body[unit_end..]);
2340 let body = replaced(
2341 &body,
2342 &[
2343 (" logos_vector_side #(", &format!(" logos_vector_side #(.CTX({c}), .CB({cb}), ")),
2344 (".port_fault(g_fault), .fault(v_fault),", ".port_fault(g_fault), .fault(v_fault), .ctx(rw_at),"),
2345 (" logos_gdn_rows #(", &format!(" logos_gdn_rows #(.CTX({c}), .CB({cb}), ")),
2346 (".rst(rst), .start(g_start), .rows(", ".rst(rst), .ctx(rw_at), .start(g_start), .rows("),
2347 ],
2348 );
2349 let wires = format!(
2350 r#" // The units' starts and the jobs they take, from the contexts' grants below.
2351 wire start, g_start, v_start, a_start;
2352 wire [{jt}:0] mjob;
2353 wire [{gt}:0] gjob;
2354 wire [{vt}:0] vjob;
2355 wire [{at}:0] ajob;
2356 wire [511:0] u_rd, w_rd;
2357 wire [{cbt}:0] rw_at;
2358 wire g_fault, v_fault;
2359 wire [3:0] u_md_ready, u_mr_valid, x_md_ready, x_mr_valid, x_mw_valid, x_mw_ready, x_mdone_valid, x_mdone_ready;
2360 wire [{dt}:0] x_mr_data, x_mw_data, u_mr_data;
2361 wire [3:0] k_md_valid, k_md_ready, k_mr_valid, k_mr_ready, pw_ready_unused, pdone_valid_unused;
2362 wire [4*({aw}+{lw}+1)-1:0] k_md_data;
2363"#,
2364 jt = j - 1,
2365 gt = g - 1,
2366 vt = v - 1,
2367 at = a - 1,
2368 cbt = cb - 1,
2369 dt = 4 * dw - 1,
2370 aw = AW,
2371 lw = LW,
2372 );
2373 let lanes_mux = " logos_channel_mux #(.P(";
2376 let lanes = body.find(lanes_mux).map(|at| (at, at + body[at..].find(");\n").expect("the lanes' multiplexer's end") + 3));
2377 let (body, channels) = match lanes {
2378 Some((at, end)) => {
2379 let ports: usize = body[at + lanes_mux.len()..].split(')').next().and_then(|n| n.parse().ok()).expect("the lanes' multiplexer's ports");
2380 let flat = format!(
2381 " logos_channel_mux #(.P({}), .NB(4), .AW({AW}), .LW({LW}), .DL({dw})) mux(.clk(clk), .rst(rst),\n \
2382 .pd_valid({{k_md_valid, c_md_valid}}), .pd_ready({{k_md_ready, c_md_ready}}), .pd_data({{k_md_data, c_md_data}}),\n \
2383 .pr_valid({{k_mr_valid, c_mr_valid}}), .pr_ready({{k_mr_ready, c_mr_ready}}), .pw_valid({{4'd0, c_mw_valid}}),\n \
2384 .pw_ready({{pw_ready_unused, c_mw_ready}}), .pw_data({{{}'d0, c_mw_data}}), .pdone_valid({{pdone_valid_unused, c_mdone_valid}}),\n \
2385 .pdone_ready({{4'hf, c_mdone_ready}}), .md_valid(md_valid), .md_ready(md_ready), .md_data(md_data), .mr_valid(mr_valid),\n \
2386 .mr_ready(mr_ready), .mw_valid(mw_valid), .mw_ready(mw_ready), .mw_data(mw_data), .mdone_valid(mdone_valid),\n \
2387 .mdone_ready(mdone_ready));\n",
2388 ports + 1,
2389 4 * dw
2390 );
2391 (format!("{}{flat}{}", &body[..at], &body[end..]), String::new())
2392 }
2393 None => (
2394 body,
2395 format!(
2396 " logos_channel_mux #(.P(2), .NB(4), .AW({AW}), .LW({LW}), .DL({dw})) cmux(.clk(clk), .rst(rst),\n \
2397 .pd_valid({{k_md_valid, p_md_valid}}), .pd_ready({{k_md_ready, x_md_ready}}), .pd_data({{k_md_data, p_md_data}}),\n \
2398 .pr_valid({{k_mr_valid, x_mr_valid}}), .pr_ready({{k_mr_ready, p_mr_ready}}), .pw_valid({{4'd0, x_mw_valid}}),\n \
2399 .pw_ready({{pw_ready_unused, x_mw_ready}}), .pw_data({{{}'d0, x_mw_data}}), .pdone_valid({{pdone_valid_unused, x_mdone_valid}}),\n \
2400 .pdone_ready({{4'hf, x_mdone_ready}}), .md_valid(md_valid), .md_ready(md_ready), .md_data(md_data), .mr_valid(mr_valid),\n \
2401 .mr_ready(mr_ready), .mw_valid(mw_valid), .mw_ready(mw_ready), .mw_data(mw_data), .mdone_valid(mdone_valid),\n \
2402 .mdone_ready(mdone_ready));\n",
2403 4 * dw
2404 ),
2405 ),
2406 };
2407 let grants = format!(
2408 r#" // The contexts' grants: a class grants the first requesting context after the one it served last.
2409 function [{cb}:0] next_of(input [{ct}:0] want, input [{cbt}:0] last);
2410 integer i, k;
2411 reg found;
2412 reg [{cbt}:0] at;
2413 begin
2414 found = 1'b0;
2415 at = last;
2416 for (i = 1; i <= {c}; i = i + 1) begin
2417 k = (last + i) % {c};
2418 if (!found && want[k]) begin found = 1'b1; at = k[{cbt}:0]; end
2419 end
2420 next_of = {{found, at}};
2421 end
2422 endfunction
2423 // The matvec class.
2424 reg [{ct}:0] mv_pend;
2425 reg [{cbt}:0] mv_ctx;
2426 reg mv_valid;
2427 wire [{ct}:0] mv_ask = go & {{{c}{{cr}}}};
2428 wire [{cb}:0] mv_next = next_of(mv_pend | mv_ask, mv_ctx);
2429 wire mv_grant = cr && mv_next[{cb}] && !busy && !(mv_valid && own[mv_ctx]);
2430 wire [{cbt}:0] mv_at = mv_grant ? mv_next[{cbt}:0] : mv_ctx;
2431 assign start = mv_grant;
2432 assign mjob = {mjob_sel};
2433 always @(posedge clk)
2434 if (rst) begin mv_pend <= {{{c}{{1'b0}}}}; mv_ctx <= {{{cb}{{1'b0}}}}; mv_valid <= 1'b0; end
2435 else begin
2436 mv_pend <= (mv_pend | mv_ask) & ~({{{{{cm}{{1'b0}}}}, mv_grant}} << mv_next[{cbt}:0]);
2437 if (mv_grant) begin mv_ctx <= mv_next[{cbt}:0]; mv_valid <= 1'b1; end
2438 else if (mv_valid && !own[mv_ctx]) mv_valid <= 1'b0;
2439 end
2440 // The row class: a request's kind, 0 a recurrence row, 1 a vector job, 2 an attention row.
2441 reg [{ct}:0] rw_pend;
2442 reg [{kt}:0] rw_kinds;
2443 (* maxfan = 256 *) reg [{cbt}:0] rw_ctx;
2444 reg rw_valid;
2445 reg [1:0] rw_kind;
2446 wire [{ct}:0] rw_ask = (gdn_go | vec_go | att_go) & {{{c}{{cr}}}};
2447 wire [{cb}:0] rw_next = next_of(rw_pend | rw_ask, rw_ctx);
2448 wire [{cbt}:0] rw_to = rw_next[{cbt}:0];
2449 wire rw_busy = g_busy || writing || v_busy || a_busy;
2450 wire rw_grant = cr && rw_next[{cb}] && !rw_busy && !(rw_valid && own[rw_ctx]);
2451 wire [1:0] rw_new = gdn_go[rw_to] ? 2'd0 : vec_go[rw_to] ? 2'd1 : 2'd2;
2452 wire [1:0] rw_k = rw_ask[rw_to] ? rw_new : rw_kinds[2*rw_to +: 2];
2453 assign g_start = rw_grant && rw_k == 2'd0;
2454 assign v_start = rw_grant && rw_k == 2'd1;
2455 assign a_start = rw_grant && rw_k == 2'd2;
2456 assign rw_at = rw_grant ? rw_to : rw_ctx;
2457 assign gjob = {gjob_sel};
2458 assign vjob = {vjob_sel};
2459 assign ajob = {ajob_sel};
2460 integer rk;
2461 always @(posedge clk)
2462 if (rst) begin rw_pend <= {{{c}{{1'b0}}}}; rw_kinds <= {{{k2}{{1'b0}}}}; rw_ctx <= {{{cb}{{1'b0}}}}; rw_valid <= 1'b0; rw_kind <= 2'd0; end
2463 else begin
2464 for (rk = 0; rk < {c}; rk = rk + 1)
2465 if (rw_ask[rk]) rw_kinds[2*rk +: 2] <= gdn_go[rk] ? 2'd0 : vec_go[rk] ? 2'd1 : 2'd2;
2466 rw_pend <= (rw_pend | rw_ask) & ~({{{{{cm}{{1'b0}}}}, rw_grant}} << rw_to);
2467 if (rw_grant) begin rw_ctx <= rw_to; rw_valid <= 1'b1; rw_kind <= rw_k; end
2468 else if (rw_valid && !own[rw_ctx]) rw_valid <= 1'b0;
2469 end
2470 // Each context's busy: its request waiting, or the class serving it busy.
2471 integer bk;
2472 always @(posedge clk)
2473 if (rst) busy_held <= {{{c}{{1'b0}}}};
2474 else if (cr)
2475 for (bk = 0; bk < {c}; bk = bk + 1)
2476 busy_held[bk] <= mv_pend[bk] || mv_ask[bk] || rw_pend[bk] || rw_ask[bk] || (mv_valid && mv_ctx == bk[{cbt}:0] && busy)
2477 || (rw_valid && rw_ctx == bk[{cbt}:0] && rw_busy);
2478 // The row class's faults, held for the context each arose in.
2479 reg [{ct}:0] gf, vf;
2480 always @(posedge clk)
2481 if (rst) begin gf <= {{{c}{{1'b0}}}}; vf <= {{{c}{{1'b0}}}}; end
2482 else if (rw_valid) begin
2483 if (rw_kind == 2'd0) gf[rw_ctx] <= g_fault;
2484 if (rw_kind == 2'd1) vf[rw_ctx] <= v_fault;
2485 end
2486 assign gdn_fault = gf;
2487 assign vec_fault = vf;
2488 // The store, a bank a context: the matvec unit's while it serves the bank's context, the row class's likewise, the
2489 // context's core's while the core holds it.
2490 wire [{st}:0] w_ra = att ? a_ra : vec ? v_ra : g_ra;
2491 wire w_we = att ? a_we : vec ? v_we : g_we;
2492 wire [{st}:0] w_wa = att ? a_wa : vec ? v_wa : g_wa;
2493 wire [511:0] w_wd = att ? a_wd : vec ? v_wd : g_wd;
2494 wire [63:0] w_wm = att ? {{64{{1'b1}}}} : vec ? v_wm : {{64{{1'b1}}}};
2495 wire [{bt}:0] banked;
2496 genvar bank_at;
2497 generate for (bank_at = 0; bank_at < {c}; bank_at = bank_at + 1) begin : bank
2498 wire mv_here = own[bank_at] && (mv_grant ? mv_next[{cbt}:0] == bank_at : mv_valid && mv_ctx == bank_at);
2499 wire rw_here = own[bank_at] && (rw_grant ? rw_to == bank_at : rw_valid && rw_ctx == bank_at);
2500 logos_bram_re_w512 #(.D({store}), .A({sa})) store(.clk(clk), .re(own[bank_at] || cr),
2501 .ra(own[bank_at] ? (mv_here ? u_ra : w_ra) : e_ra[{sa}*bank_at +: {sa}]), .rd(banked[512*bank_at +: 512]),
2502 .we(own[bank_at] ? (mv_here ? u_we : rw_here && w_we) : ce && e_we[bank_at]),
2503 .wa(own[bank_at] ? (mv_here ? u_wa : w_wa) : e_wa[{sa}*bank_at +: {sa}]),
2504 .wd(own[bank_at] ? (mv_here ? u_wd : w_wd) : e_wd[512*bank_at +: 512]),
2505 .wm(own[bank_at] ? (mv_here ? u_wm : w_wm) : e_wm[64*bank_at +: 64]));
2506 end endgenerate
2507 assign rd = banked;
2508 assign u_rd = banked[512*mv_ctx +: 512];
2509 assign w_rd = banked[512*rw_ctx +: 512];
2510 // The streaming unit's reads through a credit a channel: its runs asked only as the credit's buffer can hold them,
2511 // every line taken from the multiplexer as it comes, the unit's next line a register; each line on to the unit
2512 // through a pipe, so the unit's take and the credit's pop each end at a register.
2513 genvar kc;
2514 generate for (kc = 0; kc < 4; kc = kc + 1) begin : credit
2515 wire c_valid, c_ready;
2516 wire [{dw}-1:0] c_data;
2517 logos_read_credit #(.AW({aw}), .LW({lw}), .DL({dw})) k(.clk(clk), .rst(rst), .in_valid(u_md_valid[kc]),
2518 .in_ready(u_md_ready[kc]), .in_data(u_md_data[({aw}+{lw}+1)*kc +: {aw}+{lw}+1]), .out_valid(c_valid),
2519 .out_ready(c_ready), .out_data(c_data), .md_valid(k_md_valid[kc]),
2520 .md_ready(k_md_ready[kc]), .md_data(k_md_data[({aw}+{lw}+1)*kc +: {aw}+{lw}+1]), .mr_valid(k_mr_valid[kc]),
2521 .mr_ready(k_mr_ready[kc]), .mr_data(x_mr_data[{dw}*kc +: {dw}]));
2522 logos_pipe #(.W({dw})) to_unit(.clk(clk), .rst_n(!rst), .in_valid(c_valid), .in_ready(c_ready), .in_data(c_data),
2523 .out_valid(u_mr_valid[kc]), .out_ready(u_mr_ready[kc]), .out_data(u_mr_data[{dw}*kc +: {dw}]));
2524 end endgenerate
2525{channels} assign x_mr_data = mr_data;
2526"#,
2527 mjob_sel = pick("job", j, "mv_at"),
2528 gjob_sel = pick("gdn_job", g, "rw_at"),
2529 vjob_sel = pick("vec_job", v, "rw_at"),
2530 ajob_sel = pick("att_job", a, "rw_at"),
2531 c = c,
2532 cb = cb,
2533 ct = c - 1,
2534 cbt = cb - 1,
2535 cm = c - 1,
2536 kt = 2 * c - 1,
2537 k2 = 2 * c,
2538 st = sa - 1,
2539 sa = sa,
2540 bt = 512 * c - 1,
2541 store = p.store_beats,
2542 aw = AW,
2543 lw = LW,
2544 dw = dw,
2545 );
2546 let header = format!(
2547 r#"// A hybrid engine's user-clock side for {c} contexts: the streaming unit, the recurrence unit, the vector unit's side, the
2548// attention side and the token-row store a bank a context, the matvec class serving one context while the row class
2549// serves another (each a context's from its grant until the context's core takes its bank back), the core's bank its own
2550// otherwise, read the cycle after the engine cycle's middle (cr) and written at its step (ce).
2551module logos_engine_fast
2552 (input clk,{clk2_in} input rst, input ce, input cr, input [{ct}:0] own, input [{ct}:0] go, input [{cj}:0] job,
2553 input [{cs}:0] e_ra, input [{ct}:0] e_we, input [{cs}:0] e_wa, input [{cd}:0] e_wd, input [{cm}:0] e_wm,
2554 output [{cd}:0] rd, output reg [{ct}:0] busy_held,
2555 output [3:0] md_valid, input [3:0] md_ready, output [{mdt}:0] md_data,
2556 input [3:0] mr_valid, output [3:0] mr_ready, input [{drt}:0] mr_data,
2557 output [3:0] mw_valid, input [3:0] mw_ready, output [{drt}:0] mw_data, input [3:0] mdone_valid, output [3:0] mdone_ready,
2558 input [{ct}:0] gdn_go, input [{cg}:0] gdn_job, output [{ct}:0] gdn_fault,
2559 input [{ct}:0] vec_go, input [{cv}:0] vec_job, output [{ct}:0] vec_fault,
2560 input host_wr, input [2:0] host_table, input [31:0] host_addr, input [511:0] host_wdata,
2561 input [{ct}:0] att_go, input [{ca}:0] att_job,
2562 input [31:0] map_space, input [{awt}:0] map_base, input [4:0] map_granule_log2);
2563"#,
2564 c = c,
2565 ct = c - 1,
2566 cj = c * j - 1,
2567 cs = c * sa - 1,
2568 cd = 512 * c - 1,
2569 cm = 64 * c - 1,
2570 mdt = 4 * mdw - 1,
2571 drt = 4 * dw - 1,
2572 cg = c * g - 1,
2573 cv = c * v - 1,
2574 ca = c * a - 1,
2575 awt = AW - 1,
2576 );
2577 let body = replaced(&body, &[("@CONTEXT_WIRES@", wires.as_str()), ("@CONTEXT_GRANTS@", grants.as_str())]);
2578 format!("{}{header}{body}", &text[..text[..head].rfind("// A hybrid engine's user-clock side").expect("the fast side's comment")])
2579}
2580
2581fn vector_side_contexts(text: &str) -> String {
2584 let text = text.replace(" wire [127:0] cv_row_e = rowt[cv_row_k];\n", " wire [127:0] cv_row_e = rowt[ctx * ROW_MAX + cv_row_k];\n");
2586 replaced(
2587 &text,
2588 &[
2589 ("module logos_vector_side #(parameter ROW_MAX = 1,", "module logos_vector_side #(parameter CTX = 1, CB = 1, ROW_MAX = 1,"),
2590 (" input start, input [31:0] job_pc,", " input [CB-1:0] ctx, input start, input [31:0] job_pc,"),
2591 (" `LOGOS_RAMSTYLE_ASYNC(ROW_MAX) reg [127:0] rowt [0:ROW_MAX-1];\n", " `LOGOS_RAMSTYLE_ASYNC(CTX*ROW_MAX) reg [127:0] rowt [0:CTX*ROW_MAX-1];\n"),
2592 (" 3'd1: rowt[host_addr] <= host_wdata[127:0];\n", " 3'd1: rowt[host_addr[31:28] * ROW_MAX + host_addr[27:0]] <= host_wdata[127:0];\n"),
2593 (" row_e <= rowt[k]; slot_e <= slott[l_what[4:0]];", " row_e <= rowt[ctx * ROW_MAX + k]; slot_e <= slott[l_what[4:0]];"),
2594 (" row_e <= rowt[k]; slot_e <= slott[p_what[4:0]];", " row_e <= rowt[ctx * ROW_MAX + k]; slot_e <= slott[p_what[4:0]];"),
2595 (" always @(posedge clk) row_region <= rowt[k][31:0];\n", " always @(posedge clk) row_region <= rowt[ctx * ROW_MAX + k][31:0];\n"),
2596 ],
2597 )
2598}
2599
2600fn vector_side_conv(text: &str, lanes: usize, beats: usize) -> String {
2609 use super::fp32::F32_TO_F16_LATENCY;
2610 use super::vector::RNE_P_LATENCY;
2611 let narrow = F32_TO_F16_LATENCY.max(RNE_P_LATENCY);
2612 let prog: Vec<u64> = super::super::vector::conv(&[0, 1, 2], 3, &[4, 5, 6, 7], 8, 9).iter().map(super::simd::encode).collect();
2613 assert_eq!(prog.len(), 6, "the conv micro-program's six instructions");
2614 let prog_ok: String = prog.iter().enumerate().map(|(i, w)| format!(" && prog[{i}] == 64'h{w:016x}")).collect();
2615 let text = renamed(&renamed(text, "rq_valid", "rq_valid_s"), "rq_line", "rq_line_s");
2616 replaced(
2617 &text,
2618 &[
2619 (
2620 " output reg rq_valid_s, input rq_ready, output reg [31:0] rq_line_s, input rs_valid,",
2621 " output rq_valid, input rq_ready, output [31:0] rq_line, input rs_valid,",
2622 ),
2623 (
2624 " localparam JA = (JOB_WORDS > 1) ? $clog2(JOB_WORDS) : 1,",
2625 " // The side's own memory requests, the path's while it runs.\n reg rq_valid_s;\n reg [31:0] rq_line_s;\n localparam JA = (JOB_WORDS > 1) ? $clog2(JOB_WORDS) : 1,",
2626 ),
2627 ("FM_T = 6'd39, GNEXT = 6'd40;", "FM_T = 6'd39, GNEXT = 6'd40, CV_T = 6'd41, CV_GO = 6'd42, CV_RUN = 6'd43;"),
2628 (
2629 " always @* begin\n j_ra = pc[JA-1:0];",
2630 &format!(
2631 r#" // A convolution job (`cv`): the machine's conv job, its descriptors' shape (cv_shape), every row on logos_conv_path,
2632 // which drives the store's ports, the memory requests and the line queue while the side waits in CV_RUN. The shape is
2633 // a register set every clock: the descriptors are written only in DESC, the last of them the clock before DESC
2634 // chooses PASS, so it holds the job's own shape from PASS's first clock on.
2635 wire [31:0] cv_c = ld_a[0][63:32];
2636 reg cv_shape;
2637 always @(posedge clk) cv_shape <= !heads_first && heads == 32'd1 && nload == 8'd8 && nput == 8'd2 && nprog == 16'd6 &&
2638 ld_a[0][7:0] == 8'h30 && ld_a[1][7:0] == 8'h31 && ld_a[2][7:0] == 8'h32 &&
2639 ld_a[0][31:24] == 8'd0 && ld_a[1][31:24] == 8'd1 && ld_a[2][31:24] == 8'd2 &&
2640 ld_a[1][23:8] == ld_a[0][23:8] && ld_a[2][23:8] == ld_a[0][23:8] && ld_a[3][7:0] == 8'h03 && ld_b[3][31:0] == 32'd0 &&
2641 ld_a[4][7:0] == 8'h14 && ld_a[5][7:0] == 8'h15 && ld_a[6][7:0] == 8'h16 && ld_a[7][7:0] == 8'h17 &&
2642 ld_a[5][23:8] == ld_a[4][23:8] && ld_a[6][23:8] == ld_a[4][23:8] && ld_a[7][23:8] == ld_a[4][23:8] &&
2643 ld_b[4][31:0] == 32'd0 && ld_b[5][31:0] == cv_c && ld_b[6][31:0] == cv_c + cv_c && ld_b[7][31:0] == cv_c + cv_c + cv_c &&
2644 ld_a[1][63:32] == cv_c && ld_a[2][63:32] == cv_c && ld_a[3][63:32] == cv_c && ld_a[4][63:32] == cv_c &&
2645 ld_a[5][63:32] == cv_c && ld_a[6][63:32] == cv_c && ld_a[7][63:32] == cv_c &&
2646 pt_a[0][7:0] == 8'h09 && pt_b[0][31:0] == 32'd0 && pt_a[1][7:0] == 8'h23 && pt_a[1][23:8] == ld_a[0][23:8] &&
2647 pt_a[0][63:32] == cv_c && pt_a[1][63:32] == cv_c{prog_ok} &&
2648 cv_c[3:0] == 4'd0 && (!f16 || !cv_c[4]) && (cv_c >> 4) <= 32'd{beats};
2649 reg cv_start;
2650 // Whether a load, a store or an instruction of the job names a register past the vector unit's: each descriptor word
2651 // read (wd_q) and whether it is a load's or store's register word (wd_reg) or an instruction (wd_prog), checked the
2652 // clock after.
2653 reg wide, wd_reg, wd_prog;
2654 reg [63:0] wd_q;
2655 wire cv_on = state == CV_RUN;
2656 wire cv_busy, cv_st_we, cv_rq_valid, cv_q_v, cv_flush;
2657 wire [15:0] cv_row_k;
2658 wire [SA-1:0] cv_st_ra, cv_st_wa;
2659 wire [511:0] cv_st_wd, cv_q_data;
2660 wire [63:0] cv_q_mask;
2661 wire [31:0] cv_rq_line, cv_q_line;
2662 wire [127:0] cv_row_e = rowt[cv_row_k];
2663 reg [31:0] cv_kline, cv_beats, cv_xb, cv_xs, cv_ob, cv_os, cv_roff, cv_rbytes;
2664 reg [7:0] cv_taps;
2665 logos_conv_path #(.SA(SA), .CVB({beats}), .LANES({lanes}), .SRL(SRL)) cvp(.clk(clk), .rst(rst), .start(cv_start), .rows(nrows),
2666 .f16(f16), .k_line(cv_kline), .beats(cv_beats), .x_base(cv_xb), .x_stride(cv_xs), .o_base(cv_ob), .o_stride(cv_os),
2667 .ring_off(cv_roff), .ring_taps(cv_taps), .row_bytes(cv_rbytes), .row_k(cv_row_k), .row_e(cv_row_e), .settled(settled),
2668 .busy(cv_busy), .st_ra(cv_st_ra), .st_rd(st_rd), .st_we(cv_st_we), .st_wa(cv_st_wa), .st_wd(cv_st_wd),
2669 .rq_valid(cv_rq_valid), .rq_ready(rq_ready), .rq_line(cv_rq_line), .rs_valid(rs_valid), .rs_data(rs_data), .q_v(cv_q_v),
2670 .q_data(cv_q_data), .q_mask(cv_q_mask), .q_line(cv_q_line), .q_n(q_n), .flush(cv_flush));
2671 assign rq_valid = cv_on ? cv_rq_valid : rq_valid_s;
2672 assign rq_line = cv_on ? cv_rq_line : rq_line_s;
2673
2674 always @* begin
2675 j_ra = pc[JA-1:0];"#
2676 ),
2677 ),
2678 (
2679 " if (state == FM_ACT) st_ra = fm_from[SA-1:0] + fm_ar[SA-1:0] * fm_from[SA-1+32:32] + fm_at[SA-1:0];\n",
2680 " if (state == FM_ACT) st_ra = fm_from[SA-1:0] + fm_ar[SA-1:0] * fm_from[SA-1+32:32] + fm_at[SA-1:0];\n if (cv_on) st_ra = cv_st_ra;\n",
2681 ),
2682 (
2683 " s_issue <= 1'b0; s_wr <= 1'b0; s_len_wr <= 1'b0; st_we <= 1'b0; cb_flush <= 1'b0;\n",
2684 &format!(
2685 " s_issue <= 1'b0; s_wr <= 1'b0; s_len_wr <= 1'b0; st_we <= 1'b0; cb_flush <= 1'b0; cv_start <= 1'b0;\n \
2686 if (cv_on && cv_st_we) begin st_we <= 1'b1; st_wa <= cv_st_wa; st_wd <= cv_st_wd; st_wm <= {{64{{1'b1}}}}; end\n \
2687 if (cv_flush) cb_flush <= 1'b1;\n \
2688 wd_q <= jw; wd_reg <= 1'b0; wd_prog <= 1'b0;\n \
2689 if ((wd_reg && wd_q[3:0] >= 4'd{v}) || (wd_prog && (wd_q[11:8] >= 4'd{v} || wd_q[15:12] >= 4'd{v} || wd_q[19:16] >= 4'd{v} || wd_q[23:20] >= 4'd{v}))) wide <= 1'b1;\n",
2690 v = CONV_PATH_VREGS
2691 ),
2692 ),
2693 (
2694 " else if (fm_shape) begin",
2695 " else if (cv_shape) begin t_ra <= ld_a[4][8+TA-1:8]; b_ra <= ld_a[0][8+BA-1:8]; state <= CV_T; end\n else if (fm_shape) begin",
2696 ),
2697 (
2698 " // A load: its fields and tables;",
2699 " // A convolution: its kernel tensor's and its layer's table words read, then every row on the path.\n \
2700 CV_T: state <= CV_GO;\n CV_GO: begin\n \
2701 cv_kline <= tw[31:0] + (ld_b[4][31:0] >> 4); cv_beats <= cv_c >> 4;\n \
2702 cv_xb <= slott[ld_a[3][12:8]][31:0] + (ld_b[3][31:0] >> 4); cv_xs <= slott[ld_a[3][12:8]][63:32];\n \
2703 cv_ob <= slott[pt_a[0][12:8]][31:0] + (pt_b[0][31:0] >> 4); cv_os <= slott[pt_a[0][12:8]][63:32];\n \
2704 cv_roff <= bw[31:0]; cv_taps <= bw[71:64]; cv_rbytes <= f16 ? bw[63:32] << 1 : bw[63:32] << 2;\n \
2705 cv_start <= 1'b1; state <= CV_RUN;\n end\n \
2706 CV_RUN: if (!cv_start && !cv_busy) state <= DRAIN;\n // A load: its fields and tables;",
2707 ),
2708 (
2709 &format!(" if (d_v[{narrow}]) begin q_data[q_wr] <= o_data; q_mask[q_wr] <= o_wmask; q_line[q_wr] <= o_byte[37:6]; q_wr <= q_wr + 4'd1; end\n"),
2710 &format!(
2711 " if (d_v[{narrow}]) begin q_data[q_wr] <= o_data; q_mask[q_wr] <= o_wmask; q_line[q_wr] <= o_byte[37:6]; q_wr <= q_wr + 4'd1; end\n \
2712 if (cv_q_v) begin q_data[q_wr] <= cv_q_data; q_mask[q_wr] <= cv_q_mask; q_line[q_wr] <= cv_q_line; q_wr <= q_wr + 4'd1; end\n"
2713 ),
2714 ),
2715 (
2716 &format!(" q_n <= q_n + {{4'd0, d_v[{narrow}]}} - {{4'd0, cb_pop}};\n"),
2717 &format!(" q_n <= q_n + {{4'd0, d_v[{narrow}]}} + {{4'd0, cv_q_v}} - {{4'd0, cb_pop}};\n"),
2718 ),
2719 (
2720 ", .VREGS(16), .SREGS(8), .FU(FU), .PAIRS(PROWS), .PASSES(PASSES)) vu(",
2721 &format!(", .VREGS({CONV_PATH_VREGS}), .SREGS(8), .FU(FU), .PAIRS(PROWS), .PASSES(PASSES)) vu("),
2722 ),
2723 (
2724 "ring_open <= 1'b0;\n state <= HDR0;\n",
2725 "ring_open <= 1'b0; wide <= 1'b0;\n state <= HDR0;\n",
2726 ),
2727 (
2728 " if (dt[0] == 1'b0) ld_a[dt >> 1] <= jw; else ld_b[dt >> 1] <= jw;\n",
2729 " if (dt[0] == 1'b0) begin ld_a[dt >> 1] <= jw; wd_reg <= 1'b1; end\n else ld_b[dt >> 1] <= jw;\n",
2730 ),
2731 (
2732 " if (dt[0] == 1'b0) pt_a[(dt - {7'd0, nload, 1'b0}) >> 1] <= jw; else pt_b[(dt - {7'd0, nload, 1'b0}) >> 1] <= jw;\n",
2733 " if (dt[0] == 1'b0) begin pt_a[(dt - {7'd0, nload, 1'b0}) >> 1] <= jw; wd_reg <= 1'b1; end\n \
2734 else pt_b[(dt - {7'd0, nload, 1'b0}) >> 1] <= jw;\n",
2735 ),
2736 (
2737 " end else prog[dt - {7'd0, nload, 1'b0} - {7'd0, nput, 1'b0}] <= jw;\n",
2738 " end else begin\n prog[dt - {7'd0, nload, 1'b0} - {7'd0, nput, 1'b0}] <= jw;\n wd_prog <= 1'b1;\n end\n",
2739 ),
2740 (
2741 " else begin li <= 8'd0; gi <= 8'd0; goff <= 16'd0; gk0 <= k; gh0 <= h; state <= LOAD; end\n",
2742 " else if (wide) begin fault <= 1'b1; state <= DRAIN; end\n else begin li <= 8'd0; gi <= 8'd0; goff <= 16'd0; gk0 <= k; gh0 <= h; state <= LOAD; end\n",
2743 ),
2744 ],
2745 )
2746}
2747
2748fn gdn_rows_contexts(text: &str) -> String {
2751 replaced(
2752 text,
2753 &[
2754 ("module logos_gdn_rows #(parameter SA = 10, ROW_MAX = 1)", "module logos_gdn_rows #(parameter SA = 10, ROW_MAX = 1, CTX = 1, CB = 1)"),
2755 (" (input clk, input rst, input start, input [15:0] rows,", " (input clk, input rst, input [CB-1:0] ctx, input start, input [15:0] rows,"),
2756 (" `LOGOS_RAMSTYLE_ASYNC(ROW_MAX) reg [31:0] rowt [0:ROW_MAX-1];\n", " localparam TB = $clog2(CTX * ROW_MAX);\n `LOGOS_RAMSTYLE_ASYNC(CTX*ROW_MAX) reg [31:0] rowt [0:CTX*ROW_MAX-1];\n"),
2757 (" 3'd1: rowt[host_addr[RB-1:0]] <= host_wdata[31:0];\n", " 3'd1: rowt[host_addr[31:28] * ROW_MAX + host_addr[27:0]] <= host_wdata[31:0];\n"),
2758 (" reg [RB-1:0] r_at;\n", " reg [TB-1:0] r_at;\n"),
2759 (" r_at <= {RB{1'b0}};\n", " r_at <= ctx * ROW_MAX;\n"),
2760 ],
2761 )
2762}
2763
2764fn renamed(text: &str, from: &str, to: &str) -> String {
2767 let word = |c: u8| c.is_ascii_alphanumeric() || c == b'_' || c == b'$';
2768 let mut out = String::with_capacity(text.len());
2769 for line in text.split_inclusive('\n') {
2770 let (code, comment) = line.split_at(line.find("//").unwrap_or(line.len()));
2771 let bytes = code.as_bytes();
2772 let mut at = 0;
2773 while let Some(k) = code[at..].find(from) {
2774 let start = at + k;
2775 let end = start + from.len();
2776 let lone = (start == 0 || !(word(bytes[start - 1]) || bytes[start - 1] == b'.')) && bytes.get(end).is_none_or(|&c| !word(c));
2777 out.push_str(&code[at..start]);
2778 out.push_str(if lone { to } else { from });
2779 at = end;
2780 }
2781 out.push_str(&code[at..]);
2782 out.push_str(comment);
2783 }
2784 out
2785}
2786
2787fn replaced(text: &str, edits: &[(&str, &str)]) -> String {
2789 let mut out = text.to_string();
2790 for (from, to) in edits {
2791 assert_eq!(out.matches(from).count(), 1, "`{from}`");
2792 out = out.replace(from, to);
2793 }
2794 out
2795}
2796
2797fn spliced(text: &str, from: &str, to: &str, with: &str) -> String {
2799 assert_eq!(text.matches(from).count(), 1, "`{from}`");
2800 assert_eq!(text.matches(to).count(), 1, "`{to}`");
2801 let (a, b) = (text.find(from).unwrap(), text.find(to).unwrap());
2802 assert!(a < b, "`{from}` before `{to}`");
2803 format!("{}{with}{}", &text[..a], &text[b..])
2804}
2805
2806pub const ENGINE_MIN_DIVIDE: u32 = 6;
2810
2811fn runs_field(word: usize, bits: u32) -> String {
2813 let parts: Vec<String> = (0..4).rev().map(|c| format!("runs[{}:{}]", 256 * c + 32 * word + bits as usize - 1, 256 * c + 32 * word)).collect();
2814 format!("{{{}}}", parts.join(", "))
2815}
2816
2817const ENGINE_VERILOG: &str = r#"// The engine: the sequencer running a step's jobs over the token-row store and the card's memory.
2818module logos_engine #(parameter ROW_MAX = @ROWS@, VROWS = @VROWS@, STORE_BEATS = @STORE@, JOB_WORDS = @JOBS@,
2819 TENSORS = @TENSORS@, LAYERS = @LAYERS@, ROPES = @ROPES@, MAXL = 16, MAXP = 16, MAXI = 64)
2820 (input clk, input rst,
2821 input start, input [31:0] job_at, input [31:0] job_end, input [15:0] rows, input state_f16, input cache_int8, input two_words, input per_block,
2822 output busy, output reg fault, output reg [31:0] pc,
2823 input host_wr, input [2:0] host_table, input [31:0] host_addr, input [511:0] host_wdata,
2824 input [31:0] host_raddr, output [511:0] host_rdata,
2825 output mem_rd, output [31:0] mem_rd_line, input mem_rd_valid, input [511:0] mem_rd_data,
2826 output mem_wr, output [31:0] mem_wr_line, output [511:0] mem_wr_data, output [63:0] mem_wr_mask, input mem_wr_ready@PORTS@);
2827
2828 `LOGOS_RAMSTYLE_ASYNC(ROW_MAX) reg [127:0] rowt [0:ROW_MAX-1];
2829 `LOGOS_RAMSTYLE_ASYNC(32) reg [63:0] slott [0:31];
2830 reg [63:0] ropet [0:ROPES-1];
2831 // The job words, the tensor and block tables and the token-row store in block RAM, each read where its address is
2832 // set (`rtl::ram`): the job word at the program counter; a tensor's entry, a load's or the job's; a block's entry, a
2833 // load's, a store's or the job's layer's; the store's beat the state reads, or the host's while the engine is idle.
2834 localparam JA = (JOB_WORDS > 1) ? $clog2(JOB_WORDS) : 1, TA = (TENSORS > 1) ? $clog2(TENSORS) : 1,
2835 BA = (LAYERS > 1) ? $clog2(LAYERS) : 1, SA = (STORE_BEATS > 1) ? $clog2(STORE_BEATS) : 1;
2836 wire [63:0] jw;
2837 wire [127:0] tw;
2838 wire [511:0] bw, st_rd;
2839 reg [31:0] t_ra, b_ra, st_ra;
2840 reg st_we;
2841 reg [31:0] st_wa;
2842 reg [511:0] st_wd;
2843 reg [63:0] st_wm;
2844 reg [6:0] state;
2845 logos_ram_w64 #(.D(JOB_WORDS), .A(JA)) jobs_ram(.clk(clk), .ra(pc[JA-1:0]), .rd(jw),
2846 .we(host_wr && host_table == 3'd0), .wa(host_addr[JA-1:0]), .wd(host_wdata[63:0]), .wm(8'hff));
2847 logos_ram_w128 #(.D(TENSORS), .A(TA)) tens_ram(.clk(clk), .ra(t_ra[TA-1:0]), .rd(tw),
2848 .we(host_wr && host_table == 3'd3), .wa(host_addr[TA-1:0]), .wd(host_wdata[127:0]), .wm(16'hffff));
2849 logos_ram_w512 #(.D(LAYERS), .A(BA)) block_ram(.clk(clk), .ra(b_ra[BA-1:0]), .rd(bw),
2850 .we(host_wr && host_table == 3'd5), .wa(host_addr[BA-1:0]), .wd(host_wdata), .wm({64{1'b1}}));
2851@STORE_RAM@ assign host_rdata = st_rd;
2852
2853 // The card's memory: writes queue in block RAM and leave as the card takes them (mem_wr_ready); a read goes out only
2854 // once every write before it has left, so no read passes a write. A write the full queue cannot take faults the step.
2855 localparam [5:0] WQ = 6'd32;
2856 reg rq_v, wq_v;
2857 reg [31:0] rq_line, wq_line;
2858 reg [511:0] wq_data;
2859 reg [63:0] wq_mask;
2860 reg [5:0] wf_count;
2861 reg [4:0] wf_head, wf_tail;
2862 wire [607:0] wf_word;
2863 logos_ram_w608 #(.D(WQ), .A(5)) write_queue(.clk(clk), .ra(wf_head), .rd(wf_word), .we(wq_v), .wa(wf_tail),
2864 .wd({wq_mask, wq_line, wq_data}), .wm({76{1'b1}}));
2865 assign mem_wr = wf_count != 6'd0;
2866 assign {mem_wr_mask, mem_wr_line, mem_wr_data} = wf_word;
2867 wire wf_pop = mem_wr && mem_wr_ready;
2868 // Room for a state's write: the queue less the write in flight and a spare.
2869 wire wq_room = ({26'd0, wf_count} + {31'd0, wq_v}) < WQ - 2;
2870 reg rd_pend;
2871 reg [31:0] rd_pend_line;
2872 wire wq_empty = (wf_count == 6'd0) && !wq_v;
2873 assign mem_rd = (rq_v || rd_pend) && wq_empty;
2874 assign mem_rd_line = rd_pend ? rd_pend_line : rq_line;
2875 always @(posedge clk) begin
2876 if (rst) begin
2877 wf_count <= 6'd0; wf_head <= 5'd0; wf_tail <= 5'd0; rd_pend <= 1'b0;
2878 end else begin
2879 if (wq_v) wf_tail <= wf_tail + 5'd1;
2880 if (wf_pop) wf_head <= wf_head + 5'd1;
2881 wf_count <= wf_count + {5'd0, wq_v} - {5'd0, wf_pop};
2882 if (rq_v && !wq_empty) begin rd_pend <= 1'b1; rd_pend_line <= rq_line; end
2883 else if (mem_rd) rd_pend <= 1'b0;
2884 end
2885 end
2886
2887 // The vector unit and its ports.
2888 reg s_issue, s_wr, s_len_wr;
2889 reg [63:0] s_instr;
2890 reg [3:0] s_wr_reg, s_rd_reg, s_rd_s;
2891 reg [15:0] s_wr_row, s_rd_row;
2892 reg [511:0] s_wr_data;
2893 reg [19:0] s_len_val;
2894 wire s_busy;
2895 wire [511:0] s_rd_data;
2896 wire [31:0] s_rd_s_val;
2897 logos_simd #(.ROWS(VROWS), .VREGS(16), .SREGS(8), .FU(@FU@)) vu(.clk(clk), .rst(rst), .issue(s_issue), .instr(s_instr), .busy(s_busy),
2898 .wr(s_wr), .wr_reg(s_wr_reg), .wr_row(s_wr_row), .wr_data(s_wr_data), .len_wr(s_len_wr), .len_val(s_len_val),
2899 .rd_reg(s_rd_reg), .rd_row(s_rd_row), .rd_data(s_rd_data), .rd_s(s_rd_s), .rd_s_val(s_rd_s_val));
2900
2901 localparam IDLE = 7'd0, HDR = 7'd1, HEADS = 7'd2, FRESH = 7'd3, DESC = 7'd5, PASS = 7'd6, LOAD = 7'd7,
2902 LOAD_WAIT = 7'd8, LOAD_LEN = 7'd9, RUN = 7'd10, RUN_GAP = 7'd11, RUN_WAIT = 7'd12, PUT = 7'd13, NEXT = 7'd14,
2903@MATVEC_STATES@ DL_HDR = 7'd30, DL_ROW = 7'd31, DL_KQ_K = 7'd32, DL_KQ_Q = 7'd33, DL_KQ_L0 = 7'd34, DL_KQ_L1 = 7'd35, DL_KQ_DOT = 7'd36,
2904 DL_KQ_GAP = 7'd37, DL_KQ_WAIT = 7'd38, DL_E_RD = 7'd39, DL_E_RW = 7'd40, DL_E_OUT = 7'd41, DL_L_OUT = 7'd42,
2905 DL_CNT = 7'd43, DL_CNT_W = 7'd44, DL_APP = 7'd45, DL_FQ = 7'd46, DL_FZ = 7'd47, DL_FL0 = 7'd48, DL_FL1 = 7'd49,
2906 DL_FK = 7'd50, DL_FKW = 7'd51, DL_FV = 7'd52, DL_FVW = 7'd53, DL_FDEC = 7'd54, DL_FDECW = 7'd55, DL_FDEC_GAP = 7'd56,
2907 DL_FDEC_WAIT = 7'd57, DL_FEXP_GAP = 7'd58, DL_FEXP_WAIT = 7'd59, DL_FBETA = 7'd60, DL_FBETAW = 7'd61,
2908 DL_FB_GAP = 7'd62, DL_FB_WAIT = 7'd63, DL_FL3 = 7'd64, DL_FRUN = 7'd65, DL_FR_GAP = 7'd66, DL_FR_WAIT = 7'd67,
2909 DL_FLOG = 7'd68, DL_FY = 7'd69, DL_FC = 7'd70, DL_L_RD = 7'd71, DL_L_RW = 7'd72, DL_CNT_WR = 7'd73,
2910 AT_HDR = 7'd80, AT_ROW = 7'd81, AT_HEAD = 7'd82, AT_Q = 7'd83, AT_Q_GAP = 7'd84, AT_RUN = 7'd85, AT_SCALE = 7'd86,
2911 AT_SCALE_W = 7'd87, AT_FETCH = 7'd88, AT_FETCH_W = 7'd89, AT_KS = 7'd90, AT_GIVE = 7'd91, AT_TAKEN = 7'd92,
2912 DL_KQ_BETA = 7'd93;
2913 // A job done, the sequencer reads the next one's header, until the step's last word. Busy until then and until every
2914 // write the step made is on the card (the card port takes a write once its channel has written it): the shell takes
2915 // its channels back once the engine is idle, so a step that ends in writes leaves none of them behind.
2916 wire [6:0] next_job = (pc < job_end) ? HDR : IDLE;
2917 assign busy = state != IDLE || !wq_empty;
2918
2919 // The job's header and descriptors.
2920 reg [63:0] hdr;
2921 wire [3:0] unit = hdr[3:0];
2922 wire heads_first = hdr[4];
2923 wire [7:0] nfresh = hdr[20:13], nload = hdr[28:21], nput = hdr[36:29];
2924 wire [15:0] nprog = hdr[52:37];
2925 reg [31:0] heads;
2926 `LOGOS_RAMSTYLE_ASYNC(MAXL) reg [63:0] ld_a [0:MAXL-1];
2927 `LOGOS_RAMSTYLE_ASYNC(MAXL) reg [63:0] ld_b [0:MAXL-1];
2928 `LOGOS_RAMSTYLE_ASYNC(MAXP) reg [63:0] pt_a [0:MAXP-1];
2929 `LOGOS_RAMSTYLE_ASYNC(MAXP) reg [63:0] pt_b [0:MAXP-1];
2930 reg [63:0] prog [0:MAXI-1];
2931 reg [15:0] dc;
2932
2933
2934 // The pass: its row and head, the load, store or instruction it is at, and the beat.
2935 reg [15:0] k;
2936 reg [31:0] h;
2937 reg [7:0] li, pi;
2938 reg [15:0] ii;
2939 reg [31:0] lb, pb;
2940
2941 // The row's table entry.
2942 wire [127:0] rw = rowt[k];
2943 wire [31:0] region = rw[31:0], position = rw[63:32];
2944 wire [7:0] ring = rw[71:64];
2945
2946 // The load at hand.
2947 wire [63:0] la = ld_a[li], lbw = ld_b[li];
2948 wire [3:0] l_reg = la[3:0], l_kind = la[7:4];
2949 wire [15:0] l_what = la[23:8];
2950 wire [7:0] l_extra = la[31:24];
2951 wire [31:0] l_len = la[63:32], l_at = lbw[31:0], l_stride = lbw[63:32];
2952 wire [31:0] l_elem = l_at + h * l_stride;
2953 wire [31:0] l_beats = (l_len + 32'd15) >> 4;
2954 wire [63:0] l_slot = slott[l_what[4:0]];
2955 wire [31:0] l_store_beat = l_slot[31:0] + {16'd0, k} * l_slot[63:32] + (l_elem >> 4) + lb;
2956 wire [63:0] l_rope = ropet[l_what];
2957 wire [511:0] l_block = bw;
2958 // A ring row's index: the row's ring index plus the age, less the ring's rows once it passes them.
2959 wire [31:0] l_ring_sum = {24'd0, ring} + {24'd0, l_extra};
2960 wire [31:0] l_ring_idx = (l_ring_sum >= l_block[95:64]) ? l_ring_sum - l_block[95:64] : l_ring_sum;
2961 wire [31:0] st_bytes = state_f16 ? 32'd2 : 32'd4;
2962 wire [63:0] l_ring_byte = {32'd0, region} * 64 + l_block[31:0] + l_ring_idx * l_block[63:32] * st_bytes + lb * (state_f16 ? 32 : 64);
2963 reg [31:0] l_line;
2964 reg l_half;
2965 always @* begin
2966 l_half = 1'b0;
2967 case (l_kind)
2968 4'd1: l_line = tw[31:0] + (l_elem >> 4) + lb;
2969 4'd2: l_line = l_rope[31:0] + (((position * 2 + {31'd0, l_extra[0]}) * l_rope[63:32]) >> 4) + lb;
2970 default: begin l_line = l_ring_byte[37:6]; l_half = l_ring_byte[5]; end
2971 endcase
2972 end
2973 wire l_from_f16 = (l_kind == 4'd3) && state_f16;
2974 wire [511:0] widened;
2975 genvar g;
2976 generate
2977 for (g = 0; g < 16; g = g + 1) begin : widen
2978 logos_f16_to_f32 wv(.h(l_half ? mem_rd_data[256 + 16*g +: 16] : mem_rd_data[16*g +: 16]), .x(widened[32*g +: 32]));
2979 end
2980 endgenerate
2981
2982 // The store at hand.
2983 wire [63:0] pa = pt_a[pi], pbw = pt_b[pi];
2984 wire [3:0] p_reg = pa[3:0];
2985 wire p_scalar = pa[4];
2986 wire [2:0] p_kind = pa[7:5];
2987 wire [15:0] p_what = pa[23:8];
2988 wire p_values = pa[24];
2989 wire [1:0] p_part = pa[26:25];
2990 wire [31:0] p_len = pa[63:32], p_at = pbw[31:0], p_stride = pbw[63:32];
2991 wire [31:0] p_elem = p_at + h * p_stride;
2992 wire [31:0] p_beats = p_scalar ? 32'd1 : (p_len + 32'd15) >> 4;
2993 wire [31:0] p_lanes = p_scalar ? 32'd1 : ((p_len - (pb << 4)) >= 32'd16 ? 32'd16 : p_len - (pb << 4));
2994 wire [63:0] p_slot = slott[p_what[4:0]];
2995 wire [31:0] p_store_beat = p_slot[31:0] + {16'd0, k} * p_slot[63:32] + (p_elem >> 4) + pb;
2996 wire [511:0] p_block = bw;
2997 // The value stored: a vector register's beat, or a scalar register in lane 0.
2998 wire [511:0] p_data = p_scalar ? {480'd0, s_rd_s_val} : s_rd_data;
2999 wire [255:0] halves;
3000 wire [127:0] codes;
3001 generate
3002 for (g = 0; g < 16; g = g + 1) begin : narrow
3003 logos_f32_to_f16 nh(.x(p_data[32*g +: 32]), .h(halves[16*g +: 16]));
3004 wire signed [31:0] c;
3005 logos_rne nc(.x(p_data[32*g +: 32]), .n(c));
3006 assign codes[8*g +: 8] = c[7:0];
3007 end
3008 endgenerate
3009 // A cached row's first byte: its position's row of its head, keys or values.
3010 wire [31:0] p_cache_off = p_values ? p_block[63:32] : p_block[31:0];
3011 wire [31:0] p_cache_w = p_values ? p_block[223:192] : p_block[191:160];
3012 wire [31:0] p_scale_off = p_values ? p_block[127:96] : p_block[95:64];
3013 wire [31:0] p_cached = position * p_block[159:128] + h;
3014 reg [63:0] p_byte;
3015 always @* begin
3016 case (p_kind)
3017 3'd1: p_byte = {32'd0, region} * 64 + p_block[31:0] + {24'd0, ring} * p_block[63:32] * st_bytes + pb * (state_f16 ? 32 : 64);
3018 default: case (p_part)
3019 2'd0: p_byte = {32'd0, region} * 64 + p_cache_off + p_cached * p_cache_w * 2 + pb * 32;
3020 2'd1: p_byte = {32'd0, region} * 64 + p_cache_off + p_cached * p_cache_w + pb * 16;
3021 default: p_byte = {32'd0, region} * 64 + p_scale_off + p_cached * 2;
3022 endcase
3023 endcase
3024 end
3025 // Lane masks: the lanes below `p_lanes`.
3026 wire [15:0] lane_mask = (p_lanes >= 32'd16) ? 16'hffff : ((16'd1 << p_lanes[4:0]) - 16'd1);
3027 wire [5:0] p_off = p_byte[5:0];
3028
3029@MATVEC_DECLS@
3030@DELTA_DECLS@
3031@ATTEND_DECLS@
3032@QUANTIZER_FEEDS@
3033 integer i, wi;
3034 always @(posedge clk) begin
3035 s_issue <= 1'b0; s_wr <= 1'b0; s_len_wr <= 1'b0; rq_v <= 1'b0; wq_v <= 1'b0;@MATVEC_PULSES@
3036 @DELTA_PULSES@
3037 // The host writes the tables and the store between steps.
3038 if (host_wr) case (host_table)
3039 3'd1: rowt[host_addr] <= host_wdata[127:0];
3040 3'd2: slott[host_addr] <= host_wdata[63:0];
3041 3'd4: ropet[host_addr] <= host_wdata[63:0];
3042 default: ;
3043 endcase
3044 if (rst) begin
3045 state <= IDLE; fault <= 1'b0;
3046 end else if (wq_v && wf_count == WQ && !wf_pop) begin
3047 fault <= 1'b1; state <= IDLE;
3048 end else case (state)
3049 IDLE: if (start) begin pc <= job_at; fault <= 1'b0; state <= HDR; end
3050 HDR: begin
3051 hdr <= jw; pc <= pc + 32'd1;
3052 if (jw[3:0] == 4'd2) state <= HEADS;
3053 else if (jw[3:0] == 4'd1) state <= MV_HDR;
3054 else if (jw[3:0] == 4'd@DELTA_UNIT@) state <= DL_HDR;
3055 else if (jw[3:0] == 4'd@ATTEND_UNIT@) state <= AT_HDR;
3056 // The step's advance moves the host's positions; the card's rows keep theirs to the step's end.
3057 else if (jw[3:0] == 4'd5) state <= (pc + 32'd1 < job_end) ? HDR : IDLE;
3058 else begin fault <= 1'b1; state <= IDLE; end
3059 end
3060@MATVEC_CASES@@DELTA_CASES@
3061@ATTEND_CASES@
3062 HEADS: begin heads <= jw[31:0]; pc <= pc + 32'd1; state <= FRESH; end
3063 // A fresh slot's width is the vector the job writes; the lanes past it are no vector's, so it needs no clearing.
3064 FRESH: begin pc <= pc + {24'd0, nfresh}; dc <= 16'd0; state <= DESC; end
3065 DESC: begin
3066 // The loads' and stores' words, two each, then the instructions, a word a cycle.
3067 if (dc == 2 * nload + 2 * nput + nprog) begin k <= 16'd0; h <= 32'd0; state <= PASS; end
3068 else begin
3069 if (dc < 2 * nload) begin
3070 if (dc[0] == 1'b0) ld_a[dc >> 1] <= jw; else ld_b[dc >> 1] <= jw;
3071 end else if (dc < 2 * nload + 2 * nput) begin
3072 if (dc[0] == 1'b0) pt_a[(dc - 2 * nload) >> 1] <= jw; else pt_b[(dc - 2 * nload) >> 1] <= jw;
3073 end else prog[dc - 2 * nload - 2 * nput] <= jw;
3074 pc <= pc + 32'd1; dc <= dc + 16'd1;
3075 end
3076 end
3077 PASS: begin
3078 if ((heads_first && h == heads) || (!heads_first && k == rows) || heads == 32'd0 || rows == 16'd0) state <= next_job;
3079 else begin li <= 8'd0; lb <= 32'd0; state <= LOAD; end
3080 end
3081 LOAD: begin
3082 if (li == nload) begin ii <= 16'd0; state <= RUN; end
3083 else if (lb == l_beats) state <= LOAD_LEN;
3084 else if (l_kind == 4'd0) begin
3085 s_wr <= 1'b1; s_wr_reg <= l_reg; s_wr_row <= lb[15:0]; s_wr_data <= st_rd;
3086 lb <= lb + 32'd1;
3087 end else begin
3088 rq_v <= 1'b1; rq_line <= l_line; state <= LOAD_WAIT;
3089 end
3090 end
3091 LOAD_WAIT: if (mem_rd_valid) begin
3092 s_wr <= 1'b1; s_wr_reg <= l_reg; s_wr_row <= lb[15:0]; s_wr_data <= l_from_f16 ? widened : mem_rd_data;
3093 lb <= lb + 32'd1; state <= LOAD;
3094 end
3095 LOAD_LEN: begin
3096 s_len_wr <= 1'b1; s_wr_reg <= l_reg; s_len_val <= l_len[19:0];
3097 li <= li + 8'd1; lb <= 32'd0; state <= LOAD;
3098 end
3099 RUN: begin
3100 if (ii == nprog) begin pi <= 8'd0; pb <= 32'd0; state <= PUT; end
3101 else begin s_issue <= 1'b1; s_instr <= prog[ii]; ii <= ii + 16'd1; state <= RUN_GAP; end
3102 end
3103 RUN_GAP: state <= RUN_WAIT;
3104 RUN_WAIT: if (!s_busy) state <= RUN;
3105 PUT: begin
3106 // The register's beat is read as the beat is chosen; the write lands a cycle later.
3107 if (pi == nput) state <= NEXT;
3108 else if (pb == p_beats) begin pi <= pi + 8'd1; pb <= 32'd0; end
3109 else if (p_kind == 3'd0 || wq_room) begin
3110 // A store into the token-row store goes through its write port; one into the card's memory, here.
3111 if (p_kind != 3'd0) begin
3112 wq_v <= 1'b1; wq_line <= p_byte[37:6];
3113 wq_data <= 512'd0; wq_mask <= 64'd0;
3114 if (p_kind == 3'd1 && !state_f16) begin
3115 wq_data <= p_data;
3116 for (i = 0; i < 16; i = i + 1) if (lane_mask[i]) wq_mask[4*i +: 4] <= 4'hf;
3117 end else if (p_kind == 3'd1 || p_part == 2'd0) begin
3118 wq_data <= {256'd0, halves} << (8 * p_off);
3119 for (i = 0; i < 16; i = i + 1) if (lane_mask[i]) wq_mask[p_off + 2*i +: 2] <= 2'b11;
3120 end else if (p_part == 2'd1) begin
3121 wq_data <= {384'd0, codes} << (8 * p_off);
3122 for (i = 0; i < 16; i = i + 1) if (lane_mask[i]) wq_mask[p_off + i] <= 1'b1;
3123 end else begin
3124 wq_data <= {496'd0, halves[15:0]} << (8 * p_off);
3125 wq_mask[p_off +: 2] <= 2'b11;
3126 end
3127 end
3128 pb <= pb + 32'd1;
3129 end
3130 end
3131 NEXT: begin
3132 if (heads_first) begin
3133 if (k + 16'd1 == rows) begin k <= 16'd0; h <= h + 32'd1; end else k <= k + 16'd1;
3134 end else begin
3135 if (h + 32'd1 == heads) begin h <= 32'd0; k <= k + 16'd1; end else h <= h + 32'd1;
3136 end
3137 state <= PASS;
3138 end
3139 default: state <= IDLE;
3140 endcase
3141 end
3142
3143 // The store's beat each state reads, the host's while the engine is idle; a load's or the job's tensor; a load's, a
3144 // store's or the job's layer's block.
3145 always @* begin
3146 case (state)
3147@MATVEC_READS@ LOAD: st_ra = l_store_beat;
3148@STORE_READS@ default: st_ra = host_raddr;
3149 endcase
3150 t_ra = (state == LOAD || state == LOAD_WAIT) ? {16'd0, l_what} : {12'd0, mv_tensor};
3151 b_ra = (state == LOAD || state == LOAD_WAIT) ? {16'd0, l_what} : (state == PUT) ? {16'd0, p_what} : {24'd0, hdr[12:5]};
3152 end
3153
3154@STORE_WRITE_NOTE@ always @* begin
3155 st_we = 1'b0; st_wa = 32'd0; st_wd = 512'd0; st_wm = 64'd0;
3156 if (host_wr && host_table >= 3'd6) begin
3157 st_we = 1'b1; st_wa = host_addr; st_wd = host_wdata; st_wm = {64{1'b1}};
3158@MATVEC_WRITES@ end else if (!rst && state == PUT && pi != nput && pb != p_beats && p_kind == 3'd0) begin
3159 st_we = 1'b1; st_wa = p_store_beat;
3160 if (p_scalar) begin st_wd[32 * p_elem[3:0] +: 32] = s_rd_s_val; st_wm[4 * p_elem[3:0] +: 4] = 4'hf; end
3161 else for (wi = 0; wi < 16; wi = wi + 1)
3162 if (lane_mask[wi]) begin st_wd[32*wi +: 32] = s_rd_data[32*wi +: 32]; st_wm[4*wi +: 4] = 4'hf; end
3163 end
3164@STORE_WRITES@ end
3165
3166 // The register a store reads, chosen by the store at hand.
3167 always @* begin
3168 s_rd_reg = p_reg; s_rd_row = pb[15:0]; s_rd_s = p_reg;
3169 @DELTA_READS@
3170 end
3171endmodule
3172"#;
3173
3174struct MatvecText {
3179 ports: String,
3180 store_ram: String,
3181 decls: String,
3182 states: String,
3183 pulses: String,
3184 cases: String,
3185 reads: String,
3186 write_note: String,
3187 writes: String,
3188}
3189
3190impl MatvecText {
3191 fn reference() -> MatvecText {
3194 MatvecText {
3195 ports: String::new(),
3196 store_ram: REFERENCE_STORE_RAM.to_string(),
3197 decls: REFERENCE_MATVEC_DECLS.to_string(),
3198 states: REFERENCE_MATVEC_STATES.to_string(),
3199 pulses: REFERENCE_MATVEC_PULSES.to_string(),
3200 cases: REFERENCE_MATVEC_CASES.to_string(),
3201 reads: REFERENCE_MATVEC_READS.to_string(),
3202 write_note: REFERENCE_STORE_WRITE_NOTE.to_string(),
3203 writes: REFERENCE_MATVEC_WRITES.to_string(),
3204 }
3205 }
3206}
3207
3208impl MatvecText {
3209 fn hybrid(s: &Streaming) -> MatvecText {
3216 let job = HybridJob::BITS;
3217 MatvecText {
3218 ports: format!(
3219 ",\n output reg sm_own, output reg sm_go, output [{}:0] sm_job, input sm_busy,\n output [31:0] e_ra, output e_we, \
3220 output [31:0] e_wa, output [511:0] e_wd, output [63:0] e_wm, input [511:0] e_rd{}{}",
3221 job - 1,
3222 if s.recurrence {
3223 format!(",\n output reg sm_gdn_go, output [{}:0] sm_gdn_job, input sm_gdn_fault", GdnJob::bits(s) - 1)
3224 } else {
3225 String::new()
3226 },
3227 if s.vectors {
3228 format!(",\n output reg sm_vec_go, output [{}:0] sm_vec_job, input sm_vec_fault", VectorSideJob::BITS - 1)
3229 } else {
3230 String::new()
3231 }
3232 ) + &if s.attention { format!(",\n output reg sm_att_go, output [{}:0] sm_att_job", AttendJob::BITS - 1) } else { String::new() },
3233 store_ram: String::new(),
3234 decls: format!(
3235 r#" // The matvec job, handed to the streaming unit in the fast clock: the stream entry the job's tensor's entry addresses,
3236 // with the job's slots, token-rows and words.
3237 reg [19:0] mv_tensor;
3238 reg [4:0] mv_from, mv_to;
3239 wire [127:0] mv_entry = tw;
3240 reg [511:0] se0, se1, se2;
3241 reg [1:0] se_i;
3242 assign sm_job = {{two_words, rows[7:0], slott[mv_to], slott[mv_from], se2, se1, se0[{entry_top}:0]}};
3243 // The store, the fast clock's through the crossing.
3244 assign st_rd = e_rd;
3245 assign e_ra = st_ra;
3246 assign e_we = st_we;
3247 assign e_wa = st_wa;
3248 assign e_wd = st_wd;
3249 assign e_wm = st_wm;
3250{vector_job}@QUANTIZERS@"#,
3251 entry_top = HybridJob::ENTRY - 1,
3252 vector_job = if s.vectors { VECTOR_JOB } else { "" },
3253 ),
3254 states: format!(
3255 " MV_HDR = 7'd15, MV_SL = 7'd16, MV_SLW = 7'd17, MV_RUN = 7'd18,{}\n",
3256 if s.vectors { " V_SG = 7'd76, V_SIDE = 7'd77," } else { "" }
3257 ),
3258 pulses: format!(" sm_go <= 1'b0; if (rst) sm_own <= 1'b0;{}", if s.vectors { " sm_vec_go <= 1'b0;" } else { "" }),
3259 cases: format!(
3260 r#" MV_HDR: begin
3261 mv_tensor <= jw[19:0]; mv_from <= jw[24:20]; mv_to <= jw[29:25]; pc <= pc + 32'd1; se_i <= 2'd0;
3262 if (!per_block{one_word}) begin fault <= 1'b1; state <= IDLE; end else state <= MV_SL;
3263 end
3264 // The stream entry a line a read, then the store and the channels the unit's, and its start.
3265 MV_SL: begin
3266 if (se_i == 2'd3) begin sm_own <= 1'b1; sm_go <= 1'b1; state <= MV_RUN; end
3267 else begin rq_v <= 1'b1; rq_line <= mv_entry[31:0] + {{30'd0, se_i}}; state <= MV_SLW; end
3268 end
3269 MV_SLW: if (mem_rd_valid) begin
3270 case (se_i)
3271 2'd0: se0 <= mem_rd_data;
3272 2'd1: se1 <= mem_rd_data;
3273 default: se2 <= mem_rd_data;
3274 endcase
3275 se_i <= se_i + 2'd1; state <= MV_SL;
3276 end
3277 MV_RUN: if (!sm_go && !sm_busy) begin sm_own <= 1'b0; state <= next_job; end
3278{vector_cases}"#,
3279 one_word = if s.set.words == Words::Two { "" } else { " || two_words" },
3280 vector_cases = if s.vectors { VECTOR_CASES } else { "" },
3281 ),
3282 reads: String::new(),
3283 write_note: " // The store's write port: the host's beat; a store's lanes; the units' writes.\n".to_string(),
3284 writes: String::new(),
3285 }
3286 }
3287}
3288
3289pub struct HybridJob;
3294
3295impl HybridJob {
3296 pub const GREEDY: usize = 192;
3297 pub const FOLD: usize = Self::GREEDY + 1;
3298 pub const ENTRY: usize = 224;
3299 pub const RUNS: usize = Self::ENTRY;
3300 pub const FROM: usize = Self::RUNS + 1024;
3301 pub const TO: usize = Self::FROM + 64;
3302 pub const TOKENS: usize = Self::TO + 64;
3303 pub const TWO: usize = Self::TOKENS + 8;
3304 pub const BITS: usize = Self::TWO + 1;
3305}
3306
3307pub struct AttendJob;
3313
3314impl AttendJob {
3315 pub const REGION: usize = 0;
3316 pub const KEYS: usize = 32;
3317 pub const VALUES: usize = 64;
3318 pub const KEY_SCALES: usize = 96;
3319 pub const VALUE_SCALES: usize = 128;
3320 pub const KV_HEADS: usize = 160;
3321 pub const POSITIONS: usize = 192;
3322 pub const SCALE: usize = 224;
3323 pub const QUERY: usize = 256;
3324 pub const OUTPUT: usize = 288;
3325 pub const HEADS: usize = 320;
3326 pub const BITS: usize = 336;
3327}
3328
3329pub struct VectorSideJob;
3333
3334impl VectorSideJob {
3335 pub const PC: usize = 0;
3336 pub const ROWS: usize = 32;
3337 pub const F16: usize = 48;
3338 pub const BITS: usize = 49;
3339}
3340
3341fn vector_jobs_on_the_fast_side(template: &str, handed_off: bool) -> String {
3347 let text = replaced(
3348 template,
3349 &[
3350 (" if (jw[3:0] == 4'd2) state <= HEADS;\n", " if (jw[3:0] == 4'd2) state <= V_SG;\n"),
3351 (
3352 " reg [31:0] heads;\n `LOGOS_RAMSTYLE_ASYNC(MAXL) reg [63:0] ld_a [0:MAXL-1];\n `LOGOS_RAMSTYLE_ASYNC(MAXL) reg [63:0] ld_b [0:MAXL-1];\n `LOGOS_RAMSTYLE_ASYNC(MAXP) reg [63:0] pt_a [0:MAXP-1];\n `LOGOS_RAMSTYLE_ASYNC(MAXP) reg [63:0] pt_b [0:MAXP-1];\n reg [63:0] prog [0:MAXI-1];\n reg [15:0] dc;\n",
3353 "",
3354 ),
3355 (
3356 " // The pass: its row and head, the load, store or instruction it is at, and the beat.\n reg [15:0] k;\n reg [31:0] h;\n \
3357 reg [7:0] li, pi;\n reg [15:0] ii;\n reg [31:0] lb, pb;\n",
3358 " // The row and the head a unit's job is at.\n reg [15:0] k;\n reg [31:0] h;\n",
3359 ),
3360 (" wire [7:0] ring = rw[71:64];\n", ""),
3361 ("@MATVEC_READS@ LOAD: st_ra = l_store_beat;\n", "@MATVEC_READS@"),
3362 (
3363 " t_ra = (state == LOAD || state == LOAD_WAIT) ? {16'd0, l_what} : {12'd0, mv_tensor};\n \
3364 b_ra = (state == LOAD || state == LOAD_WAIT) ? {16'd0, l_what} : (state == PUT) ? {16'd0, p_what} : {24'd0, hdr[12:5]};\n",
3365 " t_ra = {12'd0, mv_tensor};\n b_ra = {24'd0, hdr[12:5]};\n",
3366 ),
3367 (" s_rd_reg = p_reg; s_rd_row = pb[15:0]; s_rd_s = p_reg;\n", " s_rd_reg = 4'd0; s_rd_row = 16'd0; s_rd_s = 4'd0;\n"),
3368 ],
3369 );
3370 let text = spliced(&text, " // The load at hand.\n", "@MATVEC_DECLS@", " wire [31:0] st_bytes = state_f16 ? 32'd2 : 32'd4;\n genvar g;\n\n");
3371 let text = spliced(&text, " HEADS: begin heads <= jw[31:0];", " default: state <= IDLE;\n endcase", "");
3372 let text = spliced(&text, " end else if (!rst && state == PUT && pi != nput", "@STORE_WRITES@", " end\n");
3373 for gone in [
3374 "ld_a", "pt_b", "prog[", "l_store_beat", "p_store_beat", "l_ring_byte", "p_cached", "lane_mask", "p_byte", "l_line", "HEADS:",
3375 "LOAD_WAIT:", "PUT:", "NEXT:", "state <= HEADS",
3376 ] {
3377 assert!(!text.contains(gone), "a core whose vector jobs run on the fast side keeps no `{gone}`");
3378 }
3379 if !handed_off {
3380 return text;
3381 }
3382 let text = spliced(&text, " // The vector unit and its ports.\n", " localparam IDLE = 7'd0,", "");
3383 let text = replaced(
3384 &text,
3385 &[
3386 (" s_issue <= 1'b0; s_wr <= 1'b0; s_len_wr <= 1'b0; rq_v <= 1'b0; wq_v <= 1'b0;", " rq_v <= 1'b0; wq_v <= 1'b0;"),
3387 (
3388 " // The register a store reads, chosen by the store at hand.\n always @* begin\n s_rd_reg = 4'd0; s_rd_row = 16'd0; s_rd_s = 4'd0;\n @DELTA_READS@\n end\n",
3389 "",
3390 ),
3391 ],
3392 );
3393 for gone in ["logos_simd", "s_issue", "s_rd_reg", "s_wr_data", "s_busy"] {
3394 assert!(!text.contains(gone), "a core that hands every token-row and vector job to the fast side keeps no `{gone}`");
3395 }
3396 text
3397}
3398
3399fn delta_handed_off(hd: usize, t_max: usize, normed: bool) -> UnitVerilog {
3404 let decls = format!(
3405 r#" // The recurrence unit: the job's slots and its block's place in a region, each token-row the fast side's.
3406 localparam HD = {hd}, TMAX = {t_max};
3407 localparam [6:0] DL_SG = 7'd74, DL_SIDE = 7'd75;
3408 reg [4:0] dl_qs, dl_ks, dl_vs, dl_lds, dl_dcs, dl_gts, dl_tos;
3409 reg [19:0] dl_vat;
3410 wire [511:0] dlb = bw;
3411 wire [31:0] dl_mem = dlb[127:96], dl_log = dlb[159:128], dl_entry = dlb[191:160];
3412 wire [31:0] dl_nk = dlb[223:192], dl_nv = dlb[255:224], dl_hd = dlb[287:256];
3413 wire [31:0] dl_fval = dlb[319:288], dl_fdec = dlb[351:320], dl_fgate = dlb[383:352], dl_T = dlb[415:384], dl_scale = dlb[447:416];
3414 // A slot's beat holding element `e` of the row at hand; the element is the beat's lane `e mod 16`.
3415 function [31:0] element_beat(input [63:0] slot, input [31:0] e);
3416 element_beat = slot[31:0] + {{16'd0, k}} * slot[63:32] + (e >> 4);
3417 endfunction
3418{GDN_JOB}"#
3419 );
3420 let cases = format!(
3421 r#" DL_HDR: begin
3422 dl_qs <= jw[4:0]; dl_ks <= jw[9:5]; dl_vs <= jw[14:10]; dl_lds <= jw[19:15];
3423 dl_dcs <= jw[24:20]; dl_gts <= jw[29:25]; dl_tos <= jw[34:30]; dl_vat <= jw[54:35];
3424 pc <= pc + 32'd1; k <= 16'd0; state <= DL_ROW;
3425 end
3426 DL_ROW: begin
3427 if (k == rows) state <= next_job;
3428 else if (dl_T == 32'd0) begin fault <= 1'b1; state <= IDLE; end
3429 else state <= DL_SG;
3430 end
3431{GDN_CASES}"#
3432 );
3433 let unit = UnitVerilog { decls, cases, reads: String::new(), writes: String::new() };
3434 let unit = if normed { delta_normed_core(unit) } else { unit };
3435 for gone in ["dre", "dlr", "dl_kv", "dl_qv", "DL_KQ", "DL_E_", "dl_vals", "s_issue", "s_wr"] {
3436 for text in [&unit.decls, &unit.cases] {
3437 assert!(!text.contains(gone), "a recurrence unit that hands every token-row to the fast side keeps no `{gone}`");
3438 }
3439 }
3440 unit
3441}
3442
3443fn attend_handed_off() -> UnitVerilog {
3447 let decls = r#" // The attention unit: the job's slots, heads and score scale, its block's cache in a region, each token-row the
3448 // fast side's.
3449 localparam [6:0] AT_SG = 7'd94, AT_SIDE = 7'd95;
3450 reg [4:0] at_qs, at_tos;
3451 reg [15:0] at_heads;
3452 reg [31:0] at_scale;
3453 wire [511:0] atb = bw;
3454 wire [31:0] at_keys = atb[31:0], at_vals = atb[63:32], at_kscales = atb[95:64], at_vscales = atb[127:96];
3455 wire [31:0] at_kvh = atb[159:128];
3456 wire [63:0] at_qslot = slott[at_qs], at_oslot = slott[at_tos];
3457 wire [31:0] at_qbeat = at_qslot[31:0] + {16'd0, k} * at_qslot[63:32], at_obeat = at_oslot[31:0] + {16'd0, k} * at_oslot[63:32];
3458 assign sm_att_job = {at_heads, at_obeat, at_qbeat, at_scale, position + 32'd1, at_kvh, at_vscales, at_kscales, at_vals, at_keys,
3459 region};
3460"#
3461 .to_string();
3462 let cases = r#" AT_HDR: begin
3463 at_qs <= jw[4:0]; at_tos <= jw[9:5]; at_heads <= jw[25:10]; at_scale <= jw[57:26];
3464 pc <= pc + 32'd1; k <= 16'd0; state <= AT_ROW;
3465 end
3466 AT_ROW: if (k == rows) state <= next_job; else state <= AT_SG;
3467 // A token-row on the fast side's attention side, once every write before it has left.
3468 AT_SG: if (wq_empty) begin sm_own <= 1'b1; sm_att_go <= 1'b1; state <= AT_SIDE; end
3469 AT_SIDE: if (!sm_att_go && !sm_busy) begin sm_own <= 1'b0; k <= k + 16'd1; state <= AT_ROW; end
3470"#
3471 .to_string();
3472 UnitVerilog { decls, cases, reads: String::new(), writes: String::new() }
3473}
3474
3475const VECTOR_JOB: &str = r#" // A vector job as the fast side's vector unit takes it (`VectorSideJob`): its header's place, the step's token-rows,
3477 // whether the state stores binary16.
3478 reg [31:0] vs_pc;
3479 assign sm_vec_job = {state_f16, rows, vs_pc};
3480"#;
3481
3482const VECTOR_CASES: &str = r#" // A vector job on the fast side's vector unit — its loads, its micro-program and its stores, each pass of a row and a
3485 // head — once every write before it has left; the job's words passed.
3486 V_SG: if (wq_empty) begin
3487 vs_pc <= pc - 32'd1;
3488 pc <= pc + 32'd1 + {24'd0, nfresh} + {23'd0, nload, 1'b0} + {23'd0, nput, 1'b0} + {16'd0, nprog};
3489 sm_own <= 1'b1; sm_vec_go <= 1'b1; state <= V_SIDE;
3490 end
3491 V_SIDE: if (!sm_vec_go && !sm_busy) begin
3492 sm_own <= 1'b0;
3493 if (sm_vec_fault) begin fault <= 1'b1; state <= IDLE; end else state <= next_job;
3494 end
3495"#;
3496
3497pub struct GdnJob;
3505
3506impl GdnJob {
3507 pub const ROWS: usize = 0;
3508 pub const MEMORY: usize = 16;
3509 pub const LOG: usize = 48;
3510 pub const ENTRY: usize = 80;
3511 pub const KEY_HEADS: usize = 112;
3512 pub const VALUE_HEADS: usize = 144;
3513 pub const VALUES: usize = 176;
3514 pub const DECAYS: usize = 208;
3515 pub const GATES: usize = 240;
3516 pub const SCALE: usize = 272;
3517 pub const QUERY: usize = 304;
3518 pub const OUTPUT: usize = 309;
3519 pub const KEY: usize = 314;
3520 pub const VALUE: usize = 319;
3521 pub const LOG_DECAY: usize = 324;
3522 pub const GATE: usize = 329;
3523 pub const VALUE_AT: usize = 334;
3524 pub const TOKENS: usize = 354;
3525 pub const F16: usize = 362;
3526 pub const BITS: usize = 363;
3527 pub const NORM: usize = 363;
3530 pub const Z: usize = 364;
3531 pub const EPS: usize = 369;
3532 pub const W_LINE: usize = 401;
3533 pub const NORMED_BITS: usize = 433;
3534
3535 pub fn bits(s: &Streaming) -> usize {
3537 if s.delta_norm { GdnJob::NORMED_BITS } else { GdnJob::BITS }
3538 }
3539}
3540
3541fn delta_on_the_fast_side(core: UnitVerilog) -> UnitVerilog {
3547 let decls = replaced(
3548 &core.decls,
3549 &[
3550 (" reg [19:0] dl_vat;\n", " reg [19:0] dl_vat;\n localparam [6:0] DL_SG = 7'd74, DL_SIDE = 7'd75;\n"),
3551 (
3552 " reg [31:0] dg, dr, dj, de, dl_count, dob;\n reg [1:0] dfld;\n reg [2:0] dl_ii;\n reg dl_flush;\n",
3553 " reg [31:0] dg, dr, dj, dob;\n",
3554 ),
3555 (
3556 " reg [32*HD-1:0] dl_kv, dl_qv, dl_y, dl_c;\n `LOGOS_RAMSTYLE_ASYNC(TMAX) reg [32*HD-1:0] dl_vals [0:TMAX-1];\n \
3557 reg [31:0] dl_kq, dl_decay, dl_beta, dl_dec_e, dl_beta_e;\n reg [3:0] dl_rd_reg, dl_rd_s;\n reg [15:0] dl_rd_row;\n // The vector unit's read port is the fold's while its folded query and carried values are read out: a function of\n // the state, so no power-up value of a register of its own can hand it to the fold outside those states.\n wire dl_rd = state == DL_FY || state == DL_FC;\n",
3558 " reg [32*HD-1:0] dl_kv, dl_qv;\n reg [31:0] dl_kq, dl_decay, dl_beta;\n",
3559 ),
3560 (" wire [255:0] dl_halves, dl_app_halves;\n", " wire [255:0] dl_halves;\n"),
3561 (" logos_f32_to_f16 na(.x(dl_app_beat[32*dgv +: 32]), .h(dl_app_halves[16*dgv +: 16]));\n", ""),
3562 (
3563 " // The row engines: every token's update, and a lazily flushed memory's read-out and replay.\n \
3564 reg dre_v, dre_first, dlr_v, dlr_first, dlr_log_wr;\n reg [511:0] dre_data;\n wire dre_ov, dre_ol, dlr_ov, dlr_ol, dlr_busy;\n \
3565 wire [511:0] dre_od, dlr_od;\n wire [31:0] dre_or, dlr_or;\n",
3566 " // The row engine: every token's update.\n reg dre_v, dre_first;\n reg [511:0] dre_data;\n wire dre_ov, dre_ol;\n \
3567 wire [511:0] dre_od;\n wire [31:0] dre_or;\n",
3568 ),
3569 (
3570 " wire dl_out_v = dl_lazy ? dlr_ov : dre_ov, dl_out_l = dl_lazy ? dlr_ol : dre_ol;\n \
3571 assign dl_out_beat_data = dl_lazy ? dlr_od : dre_od;\n wire [31:0] dl_out_read = dl_lazy ? dlr_or : dre_or;\n",
3572 " wire dl_out_v = dre_ov, dl_out_l = dre_ol;\n assign dl_out_beat_data = dre_od;\n wire [31:0] dl_out_read = dre_or;\n",
3573 ),
3574 ],
3575 );
3576 let decls = spliced(&decls, " // The log: its count's line, the entry at hand, a field's beat.\n", " // A state beat as the engine reads it:", "");
3577 let decls = spliced(&decls, " // An entry's key or value beat at hand during the fold", " // The row engine: every token's update.\n", "");
3578 let decls = spliced(&decls, " logos_gdn_lazy_row #(", " wire dl_lazy = dl_T != 32'd0;\n", "");
3579 let decls = spliced(&decls, " // A fold's instruction.\n", " // The read-out's element of the job's output.\n", GDN_JOB);
3580 let cases = replaced(
3581 &core.cases,
3582 &[
3583 (
3584 " else begin h <= 32'd0; dg <= 32'd0; dj <= 32'd0; state <= dl_lazy ? DL_CNT : DL_KQ_K; end\n",
3585 " else begin h <= 32'd0; dg <= 32'd0; dj <= 32'd0; state <= dl_lazy ? DL_SG : DL_KQ_K; end\n",
3586 ),
3587 (
3588 " DL_E_OUT, DL_L_OUT: begin\n if (dl_out_v && !(dl_lazy && !dl_flush)) begin\n",
3589 " DL_E_OUT: begin\n if (dl_out_v) begin\n",
3590 ),
3591 (
3592 " if (h + 32'd1 == dl_nv) begin\n if (dl_lazy) state <= DL_CNT_WR;\n \
3593 else begin k <= k + 16'd1; state <= DL_ROW; end\n end else begin\n",
3594 " if (h + 32'd1 == dl_nv) begin k <= k + 16'd1; state <= DL_ROW; end\n else begin\n",
3595 ),
3596 (" state <= dl_lazy ? DL_FQ : DL_KQ_K;\n", " state <= DL_KQ_K;\n"),
3597 (
3598 " end else begin dr <= dr + 32'd1; state <= dl_lazy ? DL_L_RD : DL_E_RD; end\n",
3599 " end else begin dr <= dr + 32'd1; state <= DL_E_RD; end\n",
3600 ),
3601 ],
3602 );
3603 let lazy = cases.find(" // A lazily flushed memory: the row's token logged after the count,").expect("the lazily flushed memory's states");
3604 assert!(cases[lazy..].trim_end().ends_with("end") && cases[lazy..].matches("DL_CNT_WR: if (wq_room) begin").count() == 1, "the lazy states end the unit's cases");
3605 let cases = format!("{}{GDN_CASES}", &cases[..lazy]);
3606 let reads = replaced(
3607 &core.reads,
3608 &[(" DL_KQ_Q, DL_FQ: st_ra = dl_qs_beat;\n", " DL_KQ_Q: st_ra = dl_qs_beat;\n"), (" DL_APP: st_ra = dl_app_src;\n", "")],
3609 );
3610 let writes = replaced(
3611 &core.writes,
3612 &[(" else if (!rst && (state == DL_E_OUT || state == DL_L_OUT) && dl_out_l) begin\n", " else if (!rst && state == DL_E_OUT && dl_out_l) begin\n")],
3613 );
3614 let unit = UnitVerilog { decls, cases, reads, writes };
3615 for gone in [
3616 "dlr", "dl_vals", "dl_y", "dl_c[", "dl_ii", "dl_rd", "dl_word", "DL_F", "DL_L_", "DL_CNT", "DL_APP", "dl_app", "dl_count", "dfld",
3617 "dl_flush", "dl_log_byte", "dl_entry_byte",
3618 ] {
3619 for text in [&unit.decls, &unit.cases, &unit.reads, &unit.writes] {
3620 assert!(!text.contains(gone), "the fast side's recurrence leaves the core no `{gone}`");
3621 }
3622 }
3623 unit
3624}
3625
3626fn delta_normed_core(unit: UnitVerilog) -> UnitVerilog {
3631 let decls = replaced(
3632 &unit.decls,
3633 &[
3634 (
3635 " localparam [6:0] DL_SG = 7'd74, DL_SIDE = 7'd75;\n",
3636 " localparam [6:0] DL_SG = 7'd74, DL_SIDE = 7'd75, DL_NORM = 7'd78, DL_WLINE = 7'd79;\n \
3637 // The job's gated norm: whether it carries one, its gate's slot, its epsilon, its weight's first line.\n \
3638 reg dl_norm;\n reg [4:0] dl_zs;\n reg [31:0] dl_eps, dl_wline;\n",
3639 ),
3640 (" assign sm_gdn_job = {state_f16,", " assign sm_gdn_job = {dl_wline, dl_eps, dl_zs, dl_norm, state_f16,"),
3641 ],
3642 );
3643 let cases = replaced(
3644 &unit.cases,
3645 &[(
3646 " pc <= pc + 32'd1; k <= 16'd0; state <= DL_ROW;\n end\n",
3647 " dl_norm <= jw[55]; dl_zs <= jw[60:56];\n pc <= pc + 32'd1; k <= 16'd0; state <= jw[55] ? DL_NORM : DL_ROW;\n end\n \
3648 // A gated norm's word: its weight's tensor, whose first line the tensor table gives a step later, and its\n \
3649 // epsilon.\n \
3650 DL_NORM: begin mv_tensor <= jw[19:0]; dl_eps <= jw[51:20]; pc <= pc + 32'd1; state <= DL_WLINE; end\n \
3651 DL_WLINE: begin dl_wline <= mv_entry[31:0]; state <= DL_ROW; end\n",
3652 )],
3653 );
3654 UnitVerilog { decls, cases, ..unit }
3655}
3656
3657const GDN_JOB: &str = r#" // A lazily flushed memory's token-rows as the fast side's recurrence unit takes them (`GdnJob`): the step's rows, the
3659 // block's places, the read-out's scale, the slots of the rows' query, output, key heads, value heads, decay exponents
3660 // and gates and the values' first element, the tokens a flush takes, whether the state stores binary16.
3661 assign sm_gdn_job = {state_f16, dl_T[7:0], dl_vat, dl_gts, dl_lds, dl_vs, dl_ks, dl_tos, dl_qs, dl_scale, dl_fgate, dl_fdec,
3662 dl_fval, dl_nv, dl_nk, dl_entry, dl_log, dl_mem, rows};
3663"#;
3664
3665const GDN_CASES: &str = r#" // A lazily flushed memory's token-rows on the fast side's recurrence unit, all of the job's at once — each row's token
3668 // logged after its log's count, the query folded back through the log for each head, each value row read through the
3669 // folded query and, once the log is full, replayed and stored, the count written back — once every write before it
3670 // has left.
3671 DL_SG: if (wq_empty) begin
3672 if (dl_hd != $unsigned(HD) || dl_T > $unsigned(TMAX)) begin fault <= 1'b1; state <= IDLE; end
3673 else begin sm_own <= 1'b1; sm_gdn_go <= 1'b1; state <= DL_SIDE; end
3674 end
3675 DL_SIDE: if (!sm_gdn_go && !sm_busy) begin
3676 sm_own <= 1'b0;
3677 if (sm_gdn_fault) begin fault <= 1'b1; state <= IDLE; end else begin k <= rows; state <= DL_ROW; end
3678 end
3679"#;
3680
3681const REFERENCE_STORE_RAM: &str = r#" logos_ram_w512 #(.D(STORE_BEATS), .A(SA)) store_ram(.clk(clk), .ra(st_ra[SA-1:0]), .rd(st_rd),
3682 .we(st_we), .wa(st_wa[SA-1:0]), .wd(st_wd), .wm(st_wm));
3683"#;
3684
3685const REFERENCE_MATVEC_DECLS: &str = r#" // The array's job: its tensor's format, rows and blocks, the token-rows' activations a weight block at a time, and
3686 // the weight stream two rows' blocks a beat, each block gathered from the lines it spans.
3687 reg [19:0] mv_tensor;
3688 reg [4:0] mv_from, mv_to;
3689 wire [127:0] mv_entry = tw;
3690 wire [7:0] mv_type = mv_entry[39:32];
3691 wire [31:0] mv_in = {8'd0, mv_entry[63:40]}, mv_out = {8'd0, mv_entry[87:64]};
3692 reg mv_known;
3693 reg [3:0] mv_fmt;
3694 reg [15:0] mv_blen, mv_bbytes;
3695 always @* begin
3696 mv_known = 1'b0; mv_fmt = 4'd0; mv_blen = 16'd32; mv_bbytes = 16'd0;
3697 case (mv_type)
3698@FORMAT_CASES@ default: ;
3699 endcase
3700 end
3701 wire [31:0] mv_blocks = mv_in / {16'd0, mv_blen};
3702 wire [31:0] mv_pairs = (mv_out + 32'd1) >> 1;
3703 wire [31:0] mv_row_bytes = mv_blocks * {16'd0, mv_bbytes};
3704 reg mv_start, act_wr;
3705 wire w_valid = (state == MV_PRESENT);
3706 reg [7:0] act_t;
3707 reg [15:0] act_b;
3708 reg [31:0] mv_p, mv_b, aj;
3709@QUANTIZERS@
3710 reg [@WIDEST@:0] w_blk1, w_blk2;
3711 reg [2559:0] gbuf;
3712 reg [63:0] g_byte;
3713 reg [3:0] g_i, g_n;
3714 reg g_two;
3715 // The lines a block of the row at hand spans from its byte in the line.
3716 wire [15:0] g_lines = (g_byte[5:0] + mv_bbytes + 16'd63) >> 6;
3717@COLUMNS@
3718 wire mv_ready = @READY@;
3719 wire mv_col_busy = @COLUMN_BUSY@;
3720 reg o_v;
3721 reg [15:0] o_p;
3722 reg [7:0] o_t;
3723 reg [31:0] o_1, o_2;
3724 always @* begin
3725 o_v = 1'b0; o_p = 16'd0; o_t = 8'd0; o_1 = 32'd0; o_2 = 32'd0;
3726@OUT_MUX@ end
3727 wire [63:0] mv_to_slot = slott[mv_to];
3728 wire [31:0] o_e = {15'd0, o_p, 1'b0};
3729 wire [31:0] o_beat = mv_to_slot[31:0] + {24'd0, o_t} * mv_to_slot[63:32] + (o_e >> 4);
3730 wire [63:0] mv_from_slot = slott[mv_from];
3731 // A gathered block: the lines it spans, from its first byte.
3732 wire [2559:0] g_shifted = gbuf >> (8 * g_byte[5:0]);
3733 wire [63:0] g_row_byte = {32'd0, mv_entry[31:0]} * 64 + mv_b * mv_bbytes;
3734"#;
3735
3736const REFERENCE_MATVEC_STATES: &str = r#" MV_HDR = 7'd15, MV_ACT = 7'd16, MV_ACT_WR = 7'd17, MV_ACT_GAP = 7'd18, MV_GO = 7'd19, MV_NEXT = 7'd20,
3737 MV_G = 7'd21, MV_GW = 7'd22, MV_PRESENT = 7'd23, MV_DRAIN = 7'd24,
3738"#;
3739
3740const REFERENCE_MATVEC_CASES: &str = r#" MV_HDR: begin
3741 mv_tensor <= jw[19:0]; mv_from <= jw[24:20]; mv_to <= jw[29:25]; pc <= pc + 32'd1;
3742 act_t <= 8'd0; act_b <= 16'd0; aj <= 32'd0;
3743 if (!per_block) begin fault <= 1'b1; state <= IDLE; end else state <= MV_ACT;
3744 end
3745 MV_ACT: begin
3746 // A token-row's weight block of activations, a beat a cycle into the quantizer, then its codes and scale into
3747 // the column.
3748 if (!mv_known) begin fault <= 1'b1; state <= IDLE; end
3749 else if ({8'd0, act_t} == {8'd0, rows}) state <= MV_GO;
3750 else if (aj < {16'd0, mv_blen} >> 4) aj <= aj + 32'd1;
3751 else state <= MV_ACT_WR;
3752 end
3753 MV_ACT_WR: if (!aq_busy) begin act_wr <= 1'b1; state <= MV_ACT_GAP; end
3754 MV_ACT_GAP: begin
3755 aj <= 32'd0;
3756 if ({16'd0, act_b} + 32'd1 == mv_blocks) begin act_b <= 16'd0; act_t <= act_t + 8'd1; end else act_b <= act_b + 16'd1;
3757 state <= MV_ACT;
3758 end
3759 MV_GO: begin mv_start <= 1'b1; mv_p <= 32'd0; mv_b <= 32'd0; state <= MV_NEXT; end
3760 MV_NEXT: begin
3761 if (mv_p == mv_pairs) state <= MV_DRAIN;
3762 else begin
3763 g_two <= 1'b0; g_i <= 4'd0;
3764 g_byte <= g_row_byte + {32'd0, mv_p} * 2 * mv_row_bytes;
3765 state <= MV_G;
3766 end
3767 end
3768 MV_G: begin
3769 g_n <= g_lines[3:0];
3770 if (g_i != 4'd0 && g_i == g_n) begin
3771 if (!g_two) begin
3772 w_blk1 <= g_shifted[@WIDEST@:0];
3773 g_two <= 1'b1; g_i <= 4'd0;
3774 // The pair's second row, or its first again past the matrix's last row.
3775 g_byte <= g_row_byte + ((2 * mv_p + 1 < mv_out) ? ({32'd0, mv_p} * 2 + 1) : ({32'd0, mv_p} * 2)) * mv_row_bytes;
3776 end else begin
3777 w_blk2 <= g_shifted[@WIDEST@:0];
3778 state <= MV_PRESENT;
3779 end
3780 end else begin
3781 rq_v <= 1'b1; rq_line <= g_byte[37:6] + g_i; state <= MV_GW;
3782 end
3783 end
3784 MV_GW: if (mem_rd_valid) begin
3785 gbuf[512 * g_i +: 512] <= mem_rd_data;
3786 g_i <= g_i + 4'd1;
3787 g_n <= g_lines[3:0];
3788 state <= MV_G;
3789 end
3790 MV_PRESENT: if (mv_ready) begin
3791 if (mv_b + 32'd1 == mv_blocks) begin mv_b <= 32'd0; mv_p <= mv_p + 32'd1; end else mv_b <= mv_b + 32'd1;
3792 state <= MV_NEXT;
3793 end
3794 MV_DRAIN: if (!mv_col_busy && !mv_start) state <= next_job;
3795"#;
3796
3797const REFERENCE_MATVEC_READS: &str = r#" MV_ACT: st_ra = mv_from_slot[31:0] + {24'd0, act_t} * mv_from_slot[63:32] + {16'd0, act_b} * ({16'd0, mv_blen} >> 4) + aj;
3798"#;
3799
3800const REFERENCE_MATVEC_WRITES: &str = r#" end else if (o_v) begin
3801 st_we = 1'b1; st_wa = o_beat;
3802 st_wd[32 * o_e[3:0] +: 32] = o_1; st_wm[4 * o_e[3:0] +: 4] = 4'hf;
3803 if (o_e + 32'd1 < mv_out) begin st_wd[32 * (o_e[3:0] + 4'd1) +: 32] = o_2; st_wm[4 * (o_e[3:0] + 4'd1) +: 4] = 4'hf; end
3804"#;
3805
3806const REFERENCE_STORE_WRITE_NOTE: &str = r#" // The store's write port: the host's beat; a column's two outputs under their lanes, the second past the matrix's
3807 // last row kept out; a store's lanes; the units' writes.
3808"#;
3809
3810const REFERENCE_MATVEC_PULSES: &str = r#" mv_start <= 1'b0; act_wr <= 1'b0;"#;
3811
3812#[derive(Default)]
3818struct UnitVerilog {
3819 decls: String,
3820 cases: String,
3821 reads: String,
3822 writes: String,
3823}
3824
3825fn delta_verilog(hd: usize, t_max: usize) -> UnitVerilog {
3826 let w = |op: Op| format!("64'h{:016x}", super::simd::encode(&op));
3827 let fold = [
3828 Op::Dot { a: 2, b: 0, d: 3 },
3829 Op::Scalar { op: Bin::Mul, a: 2, b: 3, d: 3 },
3830 Op::VecScalar { op: Bin::Mul, a: 3, s: 3, d: 4 },
3831 Op::Vec { op: Bin::Add, a: 1, b: 4, d: 1 },
3832 Op::VecScalar { op: Bin::Mul, a: 2, s: 3, d: 5 },
3833 Op::Vec { op: Bin::Sub, a: 0, b: 5, d: 6 },
3834 Op::VecScalar { op: Bin::Mul, a: 6, s: 1, d: 0 },
3835 ];
3836 let mut fold_case = String::new();
3837 for (i, op) in fold.iter().enumerate() {
3838 writeln!(fold_case, " 3'd{i}: dl_word = {};", w(*op)).unwrap();
3839 }
3840 let in_values = (0..t_max).rev().map(|e| format!("dl_vals[{e}][32*dr +: 32]")).collect::<Vec<_>>().join(", ");
3841 let decls = format!(
3842 r#" // The recurrence unit: the job's slots, its block's place in a region, the head at hand and its log.
3843 localparam HD = {hd}, TMAX = {t_max};
3844 reg [4:0] dl_qs, dl_ks, dl_vs, dl_lds, dl_dcs, dl_gts, dl_tos;
3845 reg [19:0] dl_vat;
3846 wire [511:0] dlb = bw;
3847 wire [31:0] dl_mem = dlb[127:96], dl_log = dlb[159:128], dl_entry = dlb[191:160];
3848 wire [31:0] dl_nk = dlb[223:192], dl_nv = dlb[255:224], dl_hd = dlb[287:256];
3849 wire [31:0] dl_fval = dlb[319:288], dl_fdec = dlb[351:320], dl_fgate = dlb[383:352], dl_T = dlb[415:384], dl_scale = dlb[447:416];
3850 wire [31:0] dl_beats = dl_hd >> 4;
3851 reg [31:0] dg, dr, dj, de, dl_count, dob;
3852 reg [1:0] dfld;
3853 reg [2:0] dl_ii;
3854 reg dl_flush;
3855 reg [32*HD-1:0] dl_kv, dl_qv, dl_y, dl_c;
3856 `LOGOS_RAMSTYLE_ASYNC(TMAX) reg [32*HD-1:0] dl_vals [0:TMAX-1];
3857 reg [31:0] dl_kq, dl_decay, dl_beta, dl_dec_e, dl_beta_e;
3858 reg [3:0] dl_rd_reg, dl_rd_s;
3859 reg [15:0] dl_rd_row;
3860 // The vector unit's read port is the fold's while its folded query and carried values are read out: a function of
3861 // the state, so no power-up value of a register of its own can hand it to the fold outside those states.
3862 wire dl_rd = state == DL_FY || state == DL_FC;
3863 // A slot's beat holding element `e` of the row at hand; the element is the beat's lane `e mod 16`.
3864 function [31:0] element_beat(input [63:0] slot, input [31:0] e);
3865 element_beat = slot[31:0] + {{16'd0, k}} * slot[63:32] + (e >> 4);
3866 endfunction
3867 wire [31:0] dl_v_e = {{12'd0, dl_vat}} + h * dl_hd + dr;
3868 wire [31:0] dl_v_elem = st_rd[32 * dl_v_e[3:0] +: 32];
3869 wire [31:0] dl_h_elem = st_rd[32 * h[3:0] +: 32];
3870 // A head's key or query beat: the group's head of the row at hand.
3871 wire [31:0] dl_ks_beat = slott[dl_ks][31:0] + {{16'd0, k}} * slott[dl_ks][63:32] + ((dg * dl_hd) >> 4) + dj;
3872 wire [31:0] dl_qs_beat = slott[dl_qs][31:0] + {{16'd0, k}} * slott[dl_qs][63:32] + ((dg * dl_hd) >> 4) + dj;
3873 // The state row at hand, and the line of its beat `dj`.
3874 wire [63:0] dl_row_byte = {{32'd0, region}} * 64 + dl_mem + ({{32'd0, h}} * dl_hd + dr) * dl_hd * st_bytes;
3875 wire [63:0] dl_beat_byte = dl_row_byte + dj * 16 * st_bytes;
3876 wire [63:0] dl_out_byte = dl_row_byte + dob * 16 * st_bytes;
3877 // The log: its count's line, the entry at hand, a field's beat.
3878 wire [63:0] dl_log_byte = {{32'd0, region}} * 64 + dl_log;
3879 wire [63:0] dl_entry_byte = dl_log_byte + 64 + de * dl_entry;
3880 reg [31:0] dl_src_slot_e, dl_dst_e, dl_len;
3881 reg [4:0] dl_src_slot;
3882 always @* begin
3883 case (dfld)
3884 2'd0: begin dl_src_slot = dl_ks; dl_src_slot_e = 32'd0; dl_dst_e = 32'd0; dl_len = dl_nk * dl_hd; end
3885 2'd1: begin dl_src_slot = dl_vs; dl_src_slot_e = {{12'd0, dl_vat}}; dl_dst_e = dl_fval; dl_len = dl_nv * dl_hd; end
3886 2'd2: begin dl_src_slot = dl_lds; dl_src_slot_e = 32'd0; dl_dst_e = dl_fdec; dl_len = dl_nv; end
3887 default: begin dl_src_slot = dl_gts; dl_src_slot_e = 32'd0; dl_dst_e = dl_fgate; dl_len = dl_nv; end
3888 endcase
3889 end
3890 wire [63:0] dl_app_byte = dl_log_byte + 64 + dl_count * dl_entry + (dl_dst_e + 16 * dj) * st_bytes;
3891 wire [31:0] dl_app_lanes = (dl_len - 16 * dj >= 32'd16) ? 32'd16 : dl_len - 16 * dj;
3892 wire [15:0] dl_app_mask = (dl_app_lanes >= 32'd16) ? 16'hffff : ((16'd1 << dl_app_lanes[4:0]) - 16'd1);
3893 wire [31:0] dl_app_src = slott[dl_src_slot][31:0] + {{16'd0, k}} * slott[dl_src_slot][63:32] + (dl_src_slot_e >> 4) + dj;
3894 wire [511:0] dl_app_beat = st_rd;
3895 // A state beat as the engine reads it: f32, or the line's half of binary16 widened.
3896 wire [511:0] dl_widened;
3897 wire [255:0] dl_halves, dl_app_halves;
3898 wire [511:0] dl_out_beat_data;
3899 genvar dgv;
3900 generate
3901 for (dgv = 0; dgv < 16; dgv = dgv + 1) begin : dl_lanes
3902 logos_f16_to_f32 wv(.h(dl_beat_byte[5] ? mem_rd_data[256 + 16*dgv +: 16] : mem_rd_data[16*dgv +: 16]), .x(dl_widened[32*dgv +: 32]));
3903 logos_f32_to_f16 nv(.x(dl_out_beat_data[32*dgv +: 32]), .h(dl_halves[16*dgv +: 16]));
3904 logos_f32_to_f16 na(.x(dl_app_beat[32*dgv +: 32]), .h(dl_app_halves[16*dgv +: 16]));
3905 end
3906 endgenerate
3907 // An entry's key or value beat at hand during the fold, and its line's half widened.
3908 wire [63:0] dl_fk_byte = dl_entry_byte + (dg * dl_hd + 16 * dj) * st_bytes;
3909 wire [63:0] dl_fv_byte = dl_entry_byte + (dl_fval + h * dl_hd + 16 * dj) * st_bytes;
3910 wire [63:0] dl_fbyte = (state == DL_FV || state == DL_FVW) ? dl_fv_byte : dl_fk_byte;
3911 wire [511:0] dl_fwide;
3912 generate
3913 for (dgv = 0; dgv < 16; dgv = dgv + 1) begin : dl_flanes
3914 logos_f16_to_f32 wf(.h(dl_fbyte[5] ? mem_rd_data[256 + 16*dgv +: 16] : mem_rd_data[16*dgv +: 16]), .x(dl_fwide[32*dgv +: 32]));
3915 end
3916 endgenerate
3917 // One element of a log entry's field, from its line: f32, or binary16 widened.
3918 wire [63:0] dl_elem_byte = dl_entry_byte + ((dfld == 2'd2 ? dl_fdec : dl_fgate) + h) * st_bytes;
3919 wire [31:0] dl_elem_f16;
3920 logos_f16_to_f32 we(.h(mem_rd_data[8 * dl_elem_byte[5:0] +: 16]), .x(dl_elem_f16));
3921 wire [31:0] dl_elem = state_f16 ? dl_elem_f16 : mem_rd_data[8 * dl_elem_byte[5:0] +: 32];
3922 // The row engines: every token's update, and a lazily flushed memory's read-out and replay.
3923 reg dre_v, dre_first, dlr_v, dlr_first, dlr_log_wr;
3924 reg [511:0] dre_data;
3925 wire dre_ov, dre_ol, dlr_ov, dlr_ol, dlr_busy;
3926 wire [511:0] dre_od, dlr_od;
3927 wire [31:0] dre_or, dlr_or;
3928 logos_gdn_row_engine #(.N(HD)) dre(.clk(clk), .rst(rst), .k(dl_kv), .q(dl_qv), .decay(dl_decay), .beta(dl_beta), .kq(dl_kq),
3929 .scale(dl_scale), .f16(state_f16), .in_valid(dre_v), .in_first(dre_first), .in_data(dre_data), .in_v(dl_v_elem),
3930 .out_valid(dre_ov), .out_last(dre_ol), .out_data(dre_od), .out_read(dre_or));
3931 logos_gdn_lazy_row #(.N(HD), .T_MAX(TMAX)) dlr(.clk(clk), .rst(rst), .y(dl_y), .scale(dl_scale), .f16(state_f16),
3932 .entries(dl_count[7:0]), .flush(dl_flush), .log_wr(dlr_log_wr), .log_at(de[7:0]), .log_k(dl_kv), .log_decay(dl_dec_e),
3933 .log_beta(dl_beta_e), .in_valid(dlr_v), .in_first(dlr_first), .in_data(dre_data), .in_carried(dl_c[32*dr +: 32]),
3934 .in_values({{{in_values}}}), .out_valid(dlr_ov), .out_last(dlr_ol), .out_data(dlr_od), .out_read(dlr_or), .busy(dlr_busy));
3935 wire dl_lazy = dl_T != 32'd0;
3936 wire dl_out_v = dl_lazy ? dlr_ov : dre_ov, dl_out_l = dl_lazy ? dlr_ol : dre_ol;
3937 assign dl_out_beat_data = dl_lazy ? dlr_od : dre_od;
3938 wire [31:0] dl_out_read = dl_lazy ? dlr_or : dre_or;
3939 // A fold's instruction.
3940 reg [63:0] dl_word;
3941 always @* begin
3942 case (dl_ii)
3943{fold_case} default: dl_word = 64'd0;
3944 endcase
3945 end
3946 // The read-out's element of the job's output.
3947 wire [63:0] dl_to_slot = slott[dl_tos];
3948 wire [31:0] dl_to_e = h * dl_hd + dr;
3949 wire [31:0] dl_to_beat = dl_to_slot[31:0] + {{16'd0, k}} * dl_to_slot[63:32] + (dl_to_e >> 4);
3950"#
3951 );
3952 let cases = format!(
3953 r#" DL_HDR: begin
3954 dl_qs <= jw[4:0]; dl_ks <= jw[9:5]; dl_vs <= jw[14:10]; dl_lds <= jw[19:15];
3955 dl_dcs <= jw[24:20]; dl_gts <= jw[29:25]; dl_tos <= jw[34:30]; dl_vat <= jw[54:35];
3956 pc <= pc + 32'd1; k <= 16'd0; state <= DL_ROW;
3957 end
3958 DL_ROW: begin
3959 if (k == rows) state <= next_job;
3960 else begin h <= 32'd0; dg <= 32'd0; dj <= 32'd0; state <= dl_lazy ? DL_CNT : DL_KQ_K; end
3961 end
3962 // Every token's update: the head's k·q on the vector unit, then each value row through the row engine.
3963 DL_KQ_K: begin
3964 if (dj == dl_beats) begin dj <= 32'd0; state <= DL_KQ_Q; end
3965 else begin
3966 s_wr <= 1'b1; s_wr_reg <= 4'd0; s_wr_row <= dj[15:0];
3967 s_wr_data <= st_rd;
3968 dl_kv[512 * dj +: 512] <= st_rd;
3969 dj <= dj + 32'd1;
3970 end
3971 end
3972 DL_KQ_Q: begin
3973 if (dj == dl_beats) begin dj <= 32'd0; state <= DL_KQ_L0; end
3974 else begin
3975 s_wr <= 1'b1; s_wr_reg <= 4'd1; s_wr_row <= dj[15:0];
3976 s_wr_data <= st_rd;
3977 dl_qv[512 * dj +: 512] <= st_rd;
3978 dj <= dj + 32'd1;
3979 end
3980 end
3981 DL_KQ_L0: begin s_len_wr <= 1'b1; s_wr_reg <= 4'd0; s_len_val <= dl_hd[19:0]; state <= DL_KQ_L1; end
3982 DL_KQ_L1: begin s_len_wr <= 1'b1; s_wr_reg <= 4'd1; s_len_val <= dl_hd[19:0]; state <= DL_KQ_DOT; end
3983 DL_KQ_DOT: begin s_issue <= 1'b1; s_instr <= {dot}; state <= DL_KQ_GAP; end
3984 DL_KQ_GAP: state <= DL_KQ_WAIT;
3985 DL_KQ_WAIT: if (!s_busy) begin
3986 dl_kq <= s_rd_s_val; dl_decay <= dl_h_elem; state <= DL_KQ_BETA;
3987 end
3988 DL_KQ_BETA: begin dl_beta <= dl_h_elem; dr <= 32'd0; dj <= 32'd0; state <= DL_E_RD; end
3989 DL_E_RD: begin
3990 if (dj == dl_beats) begin dob <= 32'd0; state <= DL_E_OUT; end
3991 else begin rq_v <= 1'b1; rq_line <= dl_beat_byte[37:6]; state <= DL_E_RW; end
3992 end
3993 DL_E_RW: if (mem_rd_valid) begin
3994 dre_v <= 1'b1; dre_first <= (dj == 32'd0); dre_data <= state_f16 ? dl_widened : mem_rd_data;
3995 dj <= dj + 32'd1; state <= DL_E_RD;
3996 end
3997 DL_E_OUT, DL_L_OUT: begin
3998 if (dl_out_v && !(dl_lazy && !dl_flush)) begin
3999 // An updated beat back where it was read, as the state stores it.
4000 wq_v <= 1'b1; wq_line <= dl_out_byte[37:6];
4001 wq_data <= 512'd0; wq_mask <= 64'd0;
4002 if (state_f16) begin
4003 wq_data <= {{256'd0, dl_halves}} << (8 * dl_out_byte[5:0]);
4004 wq_mask <= {{32'd0, 32'hffff_ffff}} << dl_out_byte[5:0];
4005 end else begin wq_data <= dl_out_beat_data; wq_mask <= 64'hffff_ffff_ffff_ffff; end
4006 dob <= dob + 32'd1;
4007 end
4008 if (dl_out_l) begin
4009 dj <= 32'd0;
4010 if (dr + 32'd1 == dl_hd) begin
4011 dr <= 32'd0;
4012 if (h + 32'd1 == dl_nv) begin
4013 if (dl_lazy) state <= DL_CNT_WR;
4014 else begin k <= k + 16'd1; state <= DL_ROW; end
4015 end else begin
4016 h <= h + 32'd1; dg <= (dg + 32'd1 == dl_nk) ? 32'd0 : dg + 32'd1;
4017 state <= dl_lazy ? DL_FQ : DL_KQ_K;
4018 end
4019 end else begin dr <= dr + 32'd1; state <= dl_lazy ? DL_L_RD : DL_E_RD; end
4020 end
4021 end
4022 // A lazily flushed memory: the row's token logged after the count, the query folded back through the log
4023 // for each head, each value row read through the folded query and, once the log is full, replayed and stored.
4024 DL_CNT: begin rq_v <= 1'b1; rq_line <= dl_log_byte[37:6]; state <= DL_CNT_W; end
4025 DL_CNT_W: if (mem_rd_valid) begin dl_count <= mem_rd_data[31:0]; dfld <= 2'd0; dj <= 32'd0; state <= DL_APP; end
4026 DL_APP: begin
4027 if (16 * dj >= dl_len) begin
4028 dj <= 32'd0;
4029 if (dfld == 2'd3) begin
4030 dl_count <= dl_count + 32'd1; dl_flush <= (dl_count + 32'd1 >= dl_T); h <= 32'd0; dg <= 32'd0; state <= DL_FQ;
4031 end else dfld <= dfld + 2'd1;
4032 end else if (wq_room) begin
4033 wq_v <= 1'b1; wq_line <= dl_app_byte[37:6];
4034 wq_data <= 512'd0; wq_mask <= 64'd0;
4035 if (state_f16) begin
4036 wq_data <= {{256'd0, dl_app_halves}} << (8 * dl_app_byte[5:0]);
4037 for (i = 0; i < 16; i = i + 1) if (dl_app_mask[i]) wq_mask[dl_app_byte[5:0] + 2*i +: 2] <= 2'b11;
4038 end else begin
4039 wq_data <= dl_app_beat;
4040 for (i = 0; i < 16; i = i + 1) if (dl_app_mask[i]) wq_mask[4*i +: 4] <= 4'hf;
4041 end
4042 dj <= dj + 32'd1;
4043 end
4044 end
4045 DL_FQ: begin
4046 // The fold starts from the head's group's query and no values carried.
4047 if (dj == dl_beats) begin dj <= 32'd0; state <= DL_FZ; end
4048 else begin
4049 s_wr <= 1'b1; s_wr_reg <= 4'd0; s_wr_row <= dj[15:0];
4050 s_wr_data <= st_rd;
4051 dj <= dj + 32'd1;
4052 end
4053 end
4054 DL_FZ: begin
4055 if (dj == dl_beats) begin dj <= 32'd0; state <= DL_FL0; end
4056 else begin s_wr <= 1'b1; s_wr_reg <= 4'd1; s_wr_row <= dj[15:0]; s_wr_data <= 512'd0; dj <= dj + 32'd1; end
4057 end
4058 DL_FL0: begin s_len_wr <= 1'b1; s_wr_reg <= 4'd0; s_len_val <= dl_hd[19:0]; state <= DL_FL1; end
4059 DL_FL1: begin s_len_wr <= 1'b1; s_wr_reg <= 4'd1; s_len_val <= dl_hd[19:0]; de <= dl_count - 32'd1; dj <= 32'd0; state <= DL_FK; end
4060 DL_FK: begin
4061 if (dj == dl_beats) begin dj <= 32'd0; state <= DL_FV; end
4062 else begin rq_v <= 1'b1; rq_line <= dl_fk_byte[37:6]; state <= DL_FKW; end
4063 end
4064 DL_FKW: if (mem_rd_valid) begin
4065 s_wr <= 1'b1; s_wr_reg <= 4'd2; s_wr_row <= dj[15:0];
4066 s_wr_data <= state_f16 ? dl_fwide : mem_rd_data; dl_kv[512 * dj +: 512] <= state_f16 ? dl_fwide : mem_rd_data;
4067 dj <= dj + 32'd1; state <= DL_FK;
4068 end
4069 DL_FV: begin
4070 if (dj == dl_beats) begin dj <= 32'd0; dfld <= 2'd2; state <= DL_FDEC; end
4071 else begin rq_v <= 1'b1; rq_line <= dl_fv_byte[37:6]; state <= DL_FVW; end
4072 end
4073 DL_FVW: if (mem_rd_valid) begin
4074 s_wr <= 1'b1; s_wr_reg <= 4'd3; s_wr_row <= dj[15:0];
4075 s_wr_data <= state_f16 ? dl_fwide : mem_rd_data; dl_vals[de][512 * dj +: 512] <= state_f16 ? dl_fwide : mem_rd_data;
4076 dj <= dj + 32'd1; state <= DL_FV;
4077 end
4078 DL_FDEC: begin rq_v <= 1'b1; rq_line <= dl_elem_byte[37:6]; state <= DL_FDECW; end
4079 DL_FDECW: if (mem_rd_valid) begin
4080 // The decay exponent as a constant, its exp the entry's decay; then the gate the same way.
4081 s_issue <= 1'b1; s_instr <= {{dl_elem, 32'h0001_0008}}; state <= DL_FDEC_GAP;
4082 end
4083 DL_FDEC_GAP: state <= DL_FDEC_WAIT;
4084 DL_FDEC_WAIT: if (!s_busy) begin s_issue <= 1'b1; s_instr <= {exp}; state <= DL_FEXP_GAP; end
4085 DL_FEXP_GAP: state <= DL_FEXP_WAIT;
4086 DL_FEXP_WAIT: if (!s_busy) begin dl_dec_e <= s_rd_s_val; dfld <= 2'd3; state <= DL_FBETA; end
4087 DL_FBETA: begin rq_v <= 1'b1; rq_line <= dl_elem_byte[37:6]; state <= DL_FBETAW; end
4088 DL_FBETAW: if (mem_rd_valid) begin
4089 dl_beta_e <= dl_elem; s_issue <= 1'b1; s_instr <= {{dl_elem, 32'h0002_0008}}; state <= DL_FB_GAP;
4090 end
4091 DL_FB_GAP: state <= DL_FB_WAIT;
4092 DL_FB_WAIT: if (!s_busy) begin s_len_wr <= 1'b1; s_wr_reg <= 4'd2; s_len_val <= dl_hd[19:0]; state <= DL_FL3; end
4093 DL_FL3: begin s_len_wr <= 1'b1; s_wr_reg <= 4'd3; s_len_val <= dl_hd[19:0]; dl_ii <= 3'd0; state <= DL_FRUN; end
4094 DL_FRUN: begin
4095 if (dl_ii == 3'd7) begin dlr_log_wr <= 1'b1; state <= DL_FLOG; end
4096 else begin s_issue <= 1'b1; s_instr <= dl_word; dl_ii <= dl_ii + 3'd1; state <= DL_FR_GAP; end
4097 end
4098 DL_FR_GAP: state <= DL_FR_WAIT;
4099 DL_FR_WAIT: if (!s_busy) state <= DL_FRUN;
4100 DL_FLOG: begin
4101 if (de == 32'd0) begin dj <= 32'd0; dl_rd_reg <= 4'd0; dl_rd_row <= 16'd0; state <= DL_FY; end
4102 else begin de <= de - 32'd1; dj <= 32'd0; state <= DL_FK; end
4103 end
4104 DL_FY: begin
4105 // The folded query and the values carried, read out of the vector unit a beat a cycle.
4106 if (dj == dl_beats) begin dj <= 32'd0; dl_rd_reg <= 4'd1; dl_rd_row <= 16'd0; state <= DL_FC; end
4107 else begin dl_y[512 * dj +: 512] <= s_rd_data; dj <= dj + 32'd1; dl_rd_row <= dl_rd_row + 16'd1; end
4108 end
4109 DL_FC: begin
4110 if (dj == dl_beats) begin dj <= 32'd0; dr <= 32'd0; state <= DL_L_RD; end
4111 else begin dl_c[512 * dj +: 512] <= s_rd_data; dj <= dj + 32'd1; dl_rd_row <= dl_rd_row + 16'd1; end
4112 end
4113 DL_L_RD: begin
4114 if (dj == dl_beats) begin dob <= 32'd0; state <= DL_L_OUT; end
4115 else begin rq_v <= 1'b1; rq_line <= dl_beat_byte[37:6]; state <= DL_L_RW; end
4116 end
4117 DL_L_RW: if (mem_rd_valid) begin
4118 dlr_v <= 1'b1; dlr_first <= (dj == 32'd0); dre_data <= state_f16 ? dl_widened : mem_rd_data;
4119 dj <= dj + 32'd1; state <= DL_L_RD;
4120 end
4121 DL_CNT_WR: if (wq_room) begin
4122 wq_v <= 1'b1; wq_line <= dl_log_byte[37:6];
4123 wq_data <= {{480'd0, dl_flush ? 32'd0 : dl_count}}; wq_mask <= 64'h0000_0000_0000_000f;
4124 k <= k + 16'd1; state <= DL_ROW;
4125 end
4126"#,
4127 dot = w(Op::Dot { a: 0, b: 1, d: 0 }),
4128 exp = w(Op::ScalarMap { f: Func::Exp, a: 1, d: 1 }),
4129 );
4130 let reads = r#" DL_KQ_K: st_ra = dl_ks_beat;
4131 DL_KQ_Q, DL_FQ: st_ra = dl_qs_beat;
4132 DL_KQ_WAIT: st_ra = element_beat(slott[dl_dcs], h);
4133 DL_KQ_BETA: st_ra = element_beat(slott[dl_gts], h);
4134 DL_E_RD, DL_E_RW, DL_E_OUT: st_ra = element_beat(slott[dl_vs], dl_v_e);
4135 DL_APP: st_ra = dl_app_src;
4136"#
4137 .to_string();
4138 let writes = r#" else if (!rst && (state == DL_E_OUT || state == DL_L_OUT) && dl_out_l) begin
4139 st_we = 1'b1; st_wa = dl_to_beat; st_wd[32 * dl_to_e[3:0] +: 32] = dl_out_read; st_wm[4 * dl_to_e[3:0] +: 4] = 4'hf;
4140 end
4141"#
4142 .to_string();
4143 UnitVerilog { decls, cases, reads, writes }
4144}
4145
4146fn attend_unit_verilog(hd: usize, vd: usize, p_max: usize, array: bool) -> UnitVerilog {
4152 let unit = if array {
4153 format!(
4154 r#" logos_attend_int8 #(.HD(AH), .VD(AV), .P_MAX(AP)) atu(.clk(clk), .rst(rst), .start(at_start), .positions(at_n[15:0]),
4155 .q_codes(aq_codes[16*AH-1:0]), .q_m(aq_m), .q_e(aq_e), .scale(at_scale), .busy(at_busy),
4156 .k_valid(at_kv), .k_codes(at_krow[8*AH-1:0]), .k_scale(at_kscale), .k_ready(at_kr),
4157 .s_valid(at_sv), .v_scale(at_vscale), .s_ready(at_sr),
4158 .v_valid(at_vv), .v_codes(at_vrow[8*AV-1:0]), .v_ready(at_vr),
4159 .o_valid(at_ov), .o_beat(at_ob), .o_last(at_ol));"#
4160 )
4161 } else {
4162 r#" assign at_sr = 1'b0;
4163 logos_attend_vector #(.HD(AH), .VD(AV), .P_MAX(AP)) atu(.clk(clk), .rst(rst), .start(at_start), .positions(at_n[15:0]),
4164 .q(at_q), .scale(at_scale), .busy(at_busy),
4165 .k_valid(at_kv), .k_beat(at_beat), .k_ready(at_kr),
4166 .v_valid(at_vv), .v_beat(at_beat), .v_ready(at_vr),
4167 .o_valid(at_ov), .o_beat(at_ob), .o_last(at_ol));"#
4168 .to_string()
4169 };
4170 let decls = format!(
4171 r#" // The attention unit: the job's slots, its block's cache, the row and head at hand, and the cache rows it streams.
4172 localparam AH = {hd}, AV = {vd}, AP = {p_max};
4173 // A head's beats, sixteen elements a beat, and the lines its int8 codes take.
4174 localparam [31:0] AH_BEATS = AH / 16, AV_BEATS = AV / 16, AH_LINES = (AH + 63) / 64, AV_LINES = (AV + 63) / 64;
4175 reg [4:0] at_qs, at_tos;
4176 reg [15:0] at_heads;
4177 reg [31:0] at_scale, at_g, at_n, at_kj, at_kb, at_sj, at_vj, at_vb, at_ob_i, at_lj;
4178 reg [1:0] at_what;
4179 reg [32*AH-1:0] at_q;
4180 reg at_start, at_kv, at_sv, at_vv;
4181 reg [511:0] at_line, at_beat;
4182 reg [8*AH-1:0] at_krow;
4183 reg [8*AV-1:0] at_vrow;
4184 reg [15:0] at_kscale, at_vscale, at_rowscale;
4185 wire at_busy, at_kr, at_sr, at_vr, at_ov, at_ol;
4186 wire [511:0] at_ob;
4187 wire [511:0] atb = bw;
4188 wire [31:0] at_keys = atb[31:0], at_vals = atb[63:32], at_kscales = atb[95:64], at_vscales = atb[127:96];
4189 wire [31:0] at_kvh = atb[159:128];
4190 // A cached head row's first byte, keys or values, and its scale's: position j's row of the cached head g.
4191 wire [31:0] at_kw = cache_int8 ? AH : 2 * AH, at_vw = cache_int8 ? AV : 2 * AV;
4192 wire [63:0] at_kbyte = {{32'd0, region}} * 64 + at_keys + ({{32'd0, at_kj}} * at_kvh + at_g) * at_kw;
4193 wire [63:0] at_vbyte = {{32'd0, region}} * 64 + at_vals + ({{32'd0, at_vj}} * at_kvh + at_g) * at_vw;
4194 wire [63:0] at_ksbyte = {{32'd0, region}} * 64 + at_kscales + ({{32'd0, at_kj}} * at_kvh + at_g) * 2;
4195 wire [63:0] at_vsbyte = {{32'd0, region}} * 64 + at_vscales + ({{32'd0, at_what == 2'd1 ? at_sj : at_vj}} * at_kvh + at_g) * 2;
4196 // The vector units' beat of a fetched line: its binary16 half widened, or sixteen int8 codes times their row's scale.
4197 wire [63:0] at_beat_byte = at_what == 2'd0 ? at_kbyte + at_kb * (cache_int8 ? 16 : 32) : at_vbyte + at_vb * (cache_int8 ? 16 : 32);
4198 wire [511:0] at_wide, at_deq;
4199 wire [31:0] at_scale_f;
4200 logos_f16_to_f32 asf(.h(at_rowscale), .x(at_scale_f));
4201 genvar agv;
4202 generate
4203 for (agv = 0; agv < 16; agv = agv + 1) begin : at_lanes
4204 logos_f16_to_f32 aw(.h(at_beat_byte[5] ? at_line[256 + 16*agv +: 16] : at_line[16*agv +: 16]), .x(at_wide[32*agv +: 32]));
4205 wire [7:0] code = at_line[8 * ({{2'd0, at_beat_byte[5:4]}} * 16 + agv) +: 8];
4206 wire [31:0] cf;
4207 logos_i2f ai(.n({{{{24{{code[7]}}}}, code}}), .y(cf));
4208 logos_fmul am(.a(cf), .b(at_scale_f), .y(at_deq[32*agv +: 32]));
4209 end
4210 endgenerate
4211 // The head's output beat in the job's output slot.
4212 wire [63:0] at_to_slot = slott[at_tos];
4213 wire [31:0] at_to_beat = at_to_slot[31:0] + {{16'd0, k}} * at_to_slot[63:32] + ((h * AV) >> 4) + at_ob_i;
4214{unit}
4215"#
4216 );
4217 let common = r#" AT_HDR: begin
4218 at_qs <= jw[4:0]; at_tos <= jw[9:5]; at_heads <= jw[25:10]; at_scale <= jw[57:26];
4219 pc <= pc + 32'd1; k <= 16'd0; state <= AT_ROW;
4220 end
4221 AT_ROW: begin
4222 if (k == rows) state <= next_job;
4223 else begin h <= 32'd0; state <= AT_HEAD; end
4224 end
4225 AT_HEAD: begin
4226 // The head's cached head, and its row's positions to its own.
4227 at_g <= h / ({16'd0, at_heads} / at_kvh); at_n <= position + 32'd1; at_kj <= 32'd0; at_kb <= 32'd0; at_sj <= 32'd0;
4228 at_vj <= 32'd0; at_vb <= 32'd0; at_ob_i <= 32'd0; at_lj <= 32'd0; state <= AT_Q;
4229 end
4230 AT_Q: begin
4231 // The head's query, into the unit and the activation quantizer.
4232 if (at_lj == AH_BEATS) begin at_lj <= 32'd0; state <= AT_Q_GAP; end
4233 else begin
4234 at_q[512 * at_lj +: 512] <= st_rd;
4235 at_lj <= at_lj + 32'd1;
4236 end
4237 end
4238 AT_Q_GAP: if (!aq_busy) begin at_start <= 1'b1; state <= AT_RUN; end
4239 AT_TAKEN: begin
4240 // The unit took the item on the edge it was offered: the stream moves to its next position, or beat.
4241 if (at_kv) begin
4242 at_kv <= 1'b0;
4243 if (AT_BEATS && at_kb + 32'd1 != AH_BEATS) at_kb <= at_kb + 32'd1; else begin at_kb <= 32'd0; at_kj <= at_kj + 32'd1; end
4244 end
4245 if (at_sv) begin at_sv <= 1'b0; at_sj <= at_sj + 32'd1; end
4246 if (at_vv) begin
4247 at_vv <= 1'b0;
4248 if (AT_BEATS && at_vb + 32'd1 != AV_BEATS) at_vb <= at_vb + 32'd1; else begin at_vb <= 32'd0; at_vj <= at_vj + 32'd1; end
4249 end
4250 state <= AT_RUN;
4251 end
4252"#;
4253 let run = r#" AT_RUN: begin
4254 // Serve the unit whatever it is ready for, each stream its next position; its output beats into the store.
4255 if (at_ov) at_ob_i <= at_ob_i + 32'd1;
4256 if (at_ol) begin
4257 if (h + 32'd1 == {16'd0, at_heads}) begin k <= k + 16'd1; state <= AT_ROW; end
4258 else begin h <= h + 32'd1; state <= AT_HEAD; end
4259 end
4260 else if (at_kr) begin at_what <= 2'd0; at_lj <= 32'd0; state <= AT_FETCH; end
4261 else if (at_sr) begin at_what <= 2'd1; state <= AT_SCALE; end
4262 else if (at_vr) begin at_what <= 2'd2; at_lj <= 32'd0; state <= AT_FETCH; end
4263 end
4264 AT_SCALE: begin rq_v <= 1'b1; rq_line <= at_vsbyte[37:6]; state <= AT_SCALE_W; end
4265 AT_SCALE_W: if (mem_rd_valid) begin at_vscale <= mem_rd_data[8 * at_vsbyte[5:0] +: 16]; at_sv <= 1'b1; state <= AT_TAKEN; end
4266"#;
4267 let fetch = if array {
4268 r#" AT_FETCH: begin
4269 // A row's codes, a line at a time; then a key row's scale.
4270 if (at_lj == (at_what == 2'd0 ? AH_LINES : AV_LINES)) begin
4271 if (at_what == 2'd0) begin rq_v <= 1'b1; rq_line <= at_ksbyte[37:6]; state <= AT_KS; end
4272 else begin at_vv <= 1'b1; state <= AT_TAKEN; end
4273 end else begin
4274 rq_v <= 1'b1; rq_line <= (at_what == 2'd0 ? at_kbyte[37:6] : at_vbyte[37:6]) + at_lj; state <= AT_FETCH_W;
4275 end
4276 end
4277 AT_FETCH_W: if (mem_rd_valid) begin
4278 if (at_what == 2'd0) at_krow[512 * at_lj +: 512] <= mem_rd_data; else at_vrow[512 * at_lj +: 512] <= mem_rd_data;
4279 at_lj <= at_lj + 32'd1; state <= AT_FETCH;
4280 end
4281 AT_KS: if (mem_rd_valid) begin at_kscale <= mem_rd_data[8 * at_ksbyte[5:0] +: 16]; at_kv <= 1'b1; state <= AT_TAKEN; end
4282"#
4283 } else {
4284 r#" AT_FETCH: begin rq_v <= 1'b1; rq_line <= at_beat_byte[37:6]; state <= AT_FETCH_W; end
4285 AT_FETCH_W: if (mem_rd_valid) begin
4286 at_line <= mem_rd_data;
4287 if (cache_int8) begin
4288 rq_v <= 1'b1; rq_line <= (at_what == 2'd0 ? at_ksbyte[37:6] : at_vsbyte[37:6]); state <= AT_KS;
4289 end else state <= AT_GIVE;
4290 end
4291 AT_KS: if (mem_rd_valid) begin
4292 at_rowscale <= mem_rd_data[8 * (at_what == 2'd0 ? at_ksbyte[5:0] : at_vsbyte[5:0]) +: 16]; state <= AT_GIVE;
4293 end
4294 AT_GIVE: begin
4295 at_beat <= cache_int8 ? at_deq : at_wide;
4296 if (at_what == 2'd0) at_kv <= 1'b1; else at_vv <= 1'b1;
4297 state <= AT_TAKEN;
4298 end
4299"#
4300 };
4301 let cases = format!("{common}{run}{fetch}").replace("AT_BEATS", if array { "1'b0" } else { "1'b1" });
4302 let reads = " AT_Q: st_ra = slott[at_qs][31:0] + {16'd0, k} * slott[at_qs][63:32] + ((h * AH) >> 4) + at_lj;\n".to_string();
4303 let writes = r#" else if (!rst && state == AT_RUN && at_ov) begin
4304 st_we = 1'b1; st_wa = at_to_beat; st_wd = at_ob; st_wm = {64{1'b1}};
4305 end
4306"#
4307 .to_string();
4308 UnitVerilog { decls, cases, reads, writes }
4309}