1use crate::codegen_sva::ddr4_jedec::{verilog_model, Ddr4Grade, LOOKAHEAD};
47use crate::codegen_sva::scratchpad_rtl::{decode, encode, Decode};
48
49use super::physical::card_port_verilog;
50use super::rtl::engine::{engine_verilog, hard_float_model, simulation_twin, EngineParams, ENGINE_MIN_DIVIDE};
51
52pub const BANKS: u32 = 4;
54pub const BANK_ADDRESS_BITS: u32 = 27;
56pub const BANK_BURST_BITS: u32 = 7;
58pub const HOST_ADDRESS_BITS: u32 = 51;
60pub const HOST_LINE_BITS: u32 = 512;
61pub const MMIO_WORD_ADDRESS_BITS: u32 = 15;
63
64#[derive(Clone, Copy, Debug, PartialEq, Eq)]
66pub enum LocalMemoryLine {
67 Data512,
69 DataAndEcc576,
72 Data512Secded,
76}
77
78impl LocalMemoryLine {
79 pub fn bits(self) -> u32 {
81 match self {
82 LocalMemoryLine::Data512 | LocalMemoryLine::Data512Secded => 512,
83 LocalMemoryLine::DataAndEcc576 => 576,
84 }
85 }
86
87 pub fn bus_bits(self) -> u32 {
89 match self {
90 LocalMemoryLine::Data512 => 512,
91 LocalMemoryLine::DataAndEcc576 | LocalMemoryLine::Data512Secded => 576,
92 }
93 }
94
95 pub fn secded(self) -> bool {
97 self == LocalMemoryLine::Data512Secded
98 }
99
100 fn pim_params(self) -> &'static str {
101 match self {
102 LocalMemoryLine::Data512 => "LOCAL_MEM_AVALON_MEM_PARAMS_DEFAULT",
103 LocalMemoryLine::DataAndEcc576 | LocalMemoryLine::Data512Secded => "LOCAL_MEM_AVALON_MEM_PARAMS_FULL_BUS_DEFAULT",
104 }
105 }
106
107 fn pim_width(self) -> &'static str {
108 match self {
109 LocalMemoryLine::Data512 => "LOCAL_MEM_DATA_WIDTH",
110 LocalMemoryLine::DataAndEcc576 | LocalMemoryLine::Data512Secded => "LOCAL_MEM_FULL_BUS_WIDTH",
111 }
112 }
113}
114
115pub const SECDED_WORDS: usize = 8;
119
120pub const SECDED_CHECKS: [u32; 8] = [0, 1, 2, 4, 8, 16, 32, 64];
123
124pub fn secded_check_byte(word: usize) -> usize {
132 SECDED_WORDS - 1 - word
133}
134
135fn secded_positions() -> &'static [u32; 64] {
137 static AT: std::sync::OnceLock<[u32; 64]> = std::sync::OnceLock::new();
138 AT.get_or_init(|| {
139 let mut at = [0u32; 64];
140 for (k, p) in at.iter_mut().enumerate() {
141 let cw = encode(1u64 << k);
142 *p = (0..72u32).find(|&q| cw >> q & 1 == 1 && !SECDED_CHECKS.contains(&q)).expect("a data bit has a place");
143 }
144 at
145 })
146}
147
148fn secded_check(word: u64) -> u8 {
150 let cw = encode(word);
151 SECDED_CHECKS.iter().enumerate().fold(0u8, |c, (j, &p)| c | ((cw >> p & 1) as u8) << j)
152}
153
154pub fn secded_line(data: &[u8; 64]) -> [u8; 72] {
157 let mut bus = [0u8; 72];
158 bus[..64].copy_from_slice(data);
159 for w in 0..SECDED_WORDS {
160 let word = u64::from_le_bytes(data[8 * w..8 * w + 8].try_into().expect("eight bytes"));
161 bus[64 + secded_check_byte(w)] = secded_check(word);
162 }
163 bus
164}
165
166#[derive(Clone, Copy, Debug, PartialEq, Eq)]
169pub struct SecdedRead {
170 pub data: [u8; 64],
171 pub corrected: u8,
172 pub uncorrectable: u8,
173}
174
175pub fn secded_read(bus: &[u8; 72]) -> SecdedRead {
178 let positions = secded_positions();
179 let mut out = SecdedRead { data: [0; 64], corrected: 0, uncorrectable: 0 };
180 for w in 0..SECDED_WORDS {
181 let word = u64::from_le_bytes(bus[8 * w..8 * w + 8].try_into().expect("eight bytes"));
182 let check = bus[64 + secded_check_byte(w)];
183 let mut cw = 0u128;
184 for (k, &p) in positions.iter().enumerate() {
185 cw |= ((word >> k & 1) as u128) << p;
186 }
187 for (j, &p) in SECDED_CHECKS.iter().enumerate() {
188 cw |= ((check >> j & 1) as u128) << p;
189 }
190 let (data, verdict) = decode(cw);
191 out.data[8 * w..8 * w + 8].copy_from_slice(&data.to_le_bytes());
192 match verdict {
193 Decode::NoError => {}
194 Decode::Corrected(_) | Decode::CorrectedParityBit => out.corrected |= 1 << w,
195 Decode::DoubleError => out.uncorrectable |= 1 << w,
196 }
197 }
198 out
199}
200
201pub fn secded_verilog() -> String {
204 let positions = secded_positions();
205 let mut emask = [0u64; 8];
206 for k in 0..64 {
207 let c = secded_check(1u64 << k);
208 for (j, m) in emask.iter_mut().enumerate() {
209 *m |= ((c >> j & 1) as u64) << k;
210 }
211 }
212 let smask = |i: u32| (0..64).filter(|&k| positions[k] >> i & 1 == 1).fold(0u64, |m, k| m | 1 << k);
213 let mut v = String::from(SECDED_HEAD);
214 v += "module logos_secded_word_encode(input [63:0] d, output [7:0] c);\n";
215 for (j, m) in emask.iter().enumerate() {
216 v += &format!(" assign c[{j}] = ^(d & 64'h{m:016x});\n");
217 }
218 v += "endmodule\n\n";
219 v += SECDED_WORD_DECODE_HEAD;
220 v += "module logos_secded_word_decode(input [63:0] d, input [7:0] c, output [63:0] q, output corrected,\n output uncorrectable);\n";
221 let syndrome: Vec<String> = (0..7u32).rev().map(|i| format!("^(d & 64'h{:016x}) ^ c[{}]", smask(i), i + 1)).collect();
222 v += &format!(" wire [6:0] s = {{{}}};\n", syndrome.join(",\n "));
223 v += " wire odd = ^d ^ ^c;\n";
224 v += " assign corrected = odd && s <= 7'd71;\n";
225 v += " assign uncorrectable = odd ? s > 7'd71 : s != 7'd0;\n";
226 for (k, p) in positions.iter().enumerate() {
227 v += &format!(" assign q[{k}] = d[{k}] ^ (odd && s == 7'd{p});\n");
228 }
229 v += "endmodule\n\n";
230 v += SECDED_LINE;
231 v
232}
233
234const SECDED_HEAD: &str = r##"// SECDED(72,64) on a channel line, generated from codegen_sva::scratchpad_rtl's code: each of the line's eight
235// 64-bit words is one codeword, its data bits where they are on the bus (bits 64w..64w+63) and its check byte in ECC
236// lane 7 - w (bus bits 512 + 8(7 - w) ..), check bit j the codeword's bit at position C_j, C = 0, 1, 2, 4, 8, 16, 32, 64
237// (C_0 the overall parity). Lane 7 - w puts each codeword on 72 different DQ pins whether the controller's bus word
238// runs beat by beat over all 72 pins or holds the data pins' beats beneath the ECC pins'.
239
240// A word's check byte: each bit the parity of the data bits that feed it.
241"##;
242
243const SECDED_WORD_DECODE_HEAD: &str = r##"// A word read back: syndrome bit i the parity of the codeword positions with bit i set, so a single flipped bit's
244// position; the overall parity odd for an odd count of flips. Odd with a syndrome naming a position: one flip,
245// corrected (a check bit's flip leaves the data as it is). Even with a syndrome, or odd with one naming no position:
246// uncorrectable, the data as read.
247"##;
248
249const SECDED_LINE: &str = r##"// A line coded: the data bits as they are, word w's check byte in ECC lane 7 - w.
250module logos_secded_encode(input [511:0] d, output [575:0] bus);
251 assign bus[511:0] = d;
252 genvar w;
253 generate for (w = 0; w < 8; w = w + 1) begin : word
254 logos_secded_word_encode coder(.d(d[64*w +: 64]), .c(bus[512 + 8*(7-w) +: 8]));
255 end endgenerate
256endmodule
257
258// A line read back: the data, each word corrected where it can be, and each word's verdict, bit w for word w.
259module logos_secded_decode(input [575:0] bus, output [511:0] d, output [7:0] corrected, output [7:0] uncorrectable);
260 genvar w;
261 generate for (w = 0; w < 8; w = w + 1) begin : word
262 logos_secded_word_decode decoder(.d(bus[64*w +: 64]), .c(bus[512 + 8*(7-w) +: 8]), .q(d[64*w +: 64]),
263 .corrected(corrected[w]), .uncorrectable(uncorrectable[w]));
264 end endgenerate
265endmodule
266"##;
267
268#[derive(Clone, Copy, Debug, PartialEq, Eq)]
270pub enum ChannelModel {
271 Random,
273 Ddr4,
276}
277
278#[derive(Clone, Debug, PartialEq)]
280pub struct AfuConfig {
281 pub name: &'static str,
283 pub uuid: u128,
285 pub build: u64,
287 pub line: LocalMemoryLine,
288 pub user_clock_mhz: u32,
291 pub compute_divide: u32,
294 pub max_burst: u32,
296 pub read_fifo_log2: u32,
298 pub write_fifo_log2: u32,
299 pub channel_model: ChannelModel,
301 pub engine: Option<EngineParams>,
304 pub floorplan: Option<String>,
307 pub flat: bool,
312}
313
314pub const FIM_STATIC_EXERCISERS: &str = "${OFS_ROOTDIR}/ofs-common/src/common/afu_main_std_exerciser_design_files.tcl";
317
318pub const AFU_UUID: u128 = 0x1adf73eb_54c5_5b4c_8a2e_3a459f9b2a6e;
321
322pub const ENGINE_USER_CLOCK_MHZ: u32 = 100;
325pub const ENGINE_COMPUTE_DIVIDE: u32 = 64;
326
327impl AfuConfig {
328 pub fn engine(engine: EngineParams, line: LocalMemoryLine) -> AfuConfig {
332 AfuConfig {
333 name: "logos_d5005_engine",
334 line,
335 user_clock_mhz: ENGINE_USER_CLOCK_MHZ,
336 compute_divide: ENGINE_COMPUTE_DIVIDE,
337 engine: Some(engine),
338 ..AfuConfig::d5005()
339 }
340 }
341
342 pub fn d5005() -> AfuConfig {
345 AfuConfig {
346 name: "logos_d5005_bringup",
347 uuid: AFU_UUID,
348 build: 0,
349 line: LocalMemoryLine::DataAndEcc576,
350 user_clock_mhz: 450,
351 compute_divide: 1,
352 max_burst: 64,
353 read_fifo_log2: 9,
354 write_fifo_log2: 6,
355 channel_model: ChannelModel::Random,
356 engine: None,
357 floorplan: None,
358 flat: false,
359 }
360 }
361
362 pub fn hybrid(&self) -> bool {
364 self.engine.as_ref().is_some_and(|e| e.streaming.is_some())
365 }
366
367 pub fn pe_pairs(&self) -> bool {
370 self.engine.as_ref().and_then(|e| e.streaming.as_ref()).is_some_and(|s| s.set.pe_pairs)
371 }
372
373 pub fn token_pairs(&self) -> bool {
376 self.engine.as_ref().and_then(|e| e.streaming.as_ref()).is_some_and(|s| s.set.token_pairs)
377 }
378
379 pub fn fp_hard(&self) -> bool {
382 self.engine
383 .as_ref()
384 .and_then(|e| e.streaming.as_ref())
385 .and_then(|s| s.hard_float)
386 .is_some_and(|h| h.fp_unit == super::numerics::FpUnit::Hard)
387 }
388
389 pub fn contexts(&self) -> usize {
391 self.engine.as_ref().and_then(|e| e.streaming.as_ref()).map_or(1, |s| s.contexts)
392 }
393
394 pub fn uuid_text(&self) -> String {
396 let h = format!("{:032x}", self.uuid);
397 format!("{}-{}-{}-{}-{}", &h[0..8], &h[8..12], &h[12..16], &h[16..20], &h[20..32])
398 }
399}
400
401#[derive(Clone, Copy, Debug, PartialEq, Eq)]
403pub enum Access {
404 ReadOnly,
405 ReadWrite,
406 Doorbell,
408 WriteOneToClear,
410}
411
412#[derive(Clone, Copy, Debug, PartialEq, Eq)]
414pub struct Register {
415 pub name: &'static str,
416 pub word: u32,
418 pub access: Access,
419}
420
421pub const REGISTERS: [Register; 18] = [
424 Register { name: "DFH", word: 0, access: Access::ReadOnly },
425 Register { name: "ID_L", word: 1, access: Access::ReadOnly },
426 Register { name: "ID_H", word: 2, access: Access::ReadOnly },
427 Register { name: "RSVD0", word: 3, access: Access::ReadOnly },
428 Register { name: "RSVD1", word: 4, access: Access::ReadOnly },
429 Register { name: "SCRATCH", word: 5, access: Access::ReadWrite },
430 Register { name: "BUILD_ID", word: 6, access: Access::ReadOnly },
431 Register { name: "STATUS", word: 8, access: Access::ReadOnly },
432 Register { name: "RESULT0", word: 9, access: Access::ReadOnly },
433 Register { name: "RESULT1", word: 10, access: Access::ReadOnly },
434 Register { name: "STATUS_SEQ", word: 11, access: Access::ReadOnly },
435 Register { name: "CMD_SPACE", word: 12, access: Access::ReadOnly },
436 Register { name: "ERRORS", word: 13, access: Access::WriteOneToClear },
437 Register { name: "DROPPED", word: 14, access: Access::ReadOnly },
438 Register { name: "ARG0", word: 16, access: Access::ReadWrite },
439 Register { name: "ARG1", word: 17, access: Access::ReadWrite },
440 Register { name: "ARG2", word: 18, access: Access::ReadWrite },
441 Register { name: "DOORBELL", word: 19, access: Access::Doorbell },
442];
443
444#[derive(Clone, Copy, Debug, PartialEq, Eq)]
446pub enum Op {
447 Nop,
448 Fill,
450 Check,
452 HostToMem,
454 MemToHost,
456 HostToMemPacked,
460 MemToHostPacked,
463 Inject,
466 EccCounts,
469 EngineConfig,
473 EngineTable,
476 EngineRun,
480 StoreRead,
483}
484
485impl Op {
486 pub const ALL: [Op; 13] = [
487 Op::Nop,
488 Op::Fill,
489 Op::Check,
490 Op::HostToMem,
491 Op::MemToHost,
492 Op::HostToMemPacked,
493 Op::MemToHostPacked,
494 Op::Inject,
495 Op::EccCounts,
496 Op::EngineConfig,
497 Op::EngineTable,
498 Op::EngineRun,
499 Op::StoreRead,
500 ];
501
502 pub fn code(self) -> u8 {
503 match self {
504 Op::Nop => 0,
505 Op::Fill => 1,
506 Op::Check => 2,
507 Op::HostToMem => 3,
508 Op::MemToHost => 4,
509 Op::HostToMemPacked => 5,
510 Op::MemToHostPacked => 6,
511 Op::Inject => 7,
512 Op::EccCounts => 8,
513 Op::EngineConfig => 9,
514 Op::EngineTable => 10,
515 Op::EngineRun => 11,
516 Op::StoreRead => 12,
517 }
518 }
519
520 pub fn name(self) -> &'static str {
521 match self {
522 Op::Nop => "NOP",
523 Op::Fill => "FILL",
524 Op::Check => "CHECK",
525 Op::HostToMem => "HOST_TO_MEM",
526 Op::MemToHost => "MEM_TO_HOST",
527 Op::HostToMemPacked => "HOST_TO_MEM_PACKED",
528 Op::MemToHostPacked => "MEM_TO_HOST_PACKED",
529 Op::Inject => "INJECT",
530 Op::EccCounts => "ECC_COUNTS",
531 Op::EngineConfig => "ENGINE_CONFIG",
532 Op::EngineTable => "ENGINE_TABLE",
533 Op::EngineRun => "ENGINE_RUN",
534 Op::StoreRead => "STORE_READ",
535 }
536 }
537}
538
539#[derive(Clone, Debug, PartialEq, Eq)]
544pub struct EngineProgram {
545 pub knobs: u8,
547 pub space: u64,
549 pub base: u64,
550 pub granule_log2: u32,
551 pub job_words: usize,
553 pub residual: (u32, u32),
555 pub logits: (u32, u32),
556 pub greedy: bool,
559 pub store_beats: u32,
561 pub tokens: usize,
563 pub hidden: usize,
564 pub vocab: usize,
565 pub ring_rows: usize,
567 pub regions: u64,
569 pub region: u64,
570 pub sequences: usize,
571 pub ctx: usize,
572 pub channel_lines: Vec<u64>,
574 pub streaming: String,
577 pub contexts: usize,
580 pub fp_unit: super::numerics::FpUnit,
583}
584
585fn host_line(bits: &[u8]) -> [u8; 64] {
587 let mut line = [0u8; 64];
588 line[..bits.len()].copy_from_slice(bits);
589 line
590}
591
592pub fn job_listing(jobs: &[super::machine::Job]) -> String {
597 use std::fmt::Write;
598 let mut listing = String::from("job 0 word 0 Embed\n");
599 let mut word = 0;
600 for (j, job) in jobs.iter().enumerate() {
601 writeln!(listing, "job {} word {word} {}", j + 1, format!("{job:?}").chars().take(240).collect::<String>()).unwrap();
602 word += super::machine::encode(std::slice::from_ref(job)).len();
603 }
604 listing
605}
606
607pub fn job_listing_of(dir: &std::path::Path) -> std::io::Result<String> {
610 let table = std::fs::read(dir.join("table0.bin"))?;
611 let words: Vec<u64> = table.chunks_exact(64).map(|line| u64::from_le_bytes(line[..8].try_into().unwrap())).collect();
612 let jobs = super::machine::decode(&words).map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, format!("{}: {e}", dir.display())))?;
613 Ok(job_listing(&jobs))
614}
615
616pub fn write_engine_program(machine: &super::machine::Machine, map: &super::physical::PhysicalMap, dir: &std::path::Path) -> std::io::Result<EngineProgram> {
623 assert_eq!(map.tensors_end, 0, "the reference engine reads its weights through the card port, over a flat map");
624 assert!(map.image.tensors.is_empty(), "a flat map has no weight image");
625 let channels = map.channel_bytes(machine.memory(), &vec![Vec::new(); map.channel_lines.len()]);
626 write_program(machine, map, &|at| Some(at), &channels, String::new(), false, 1, dir)
627}
628
629pub fn write_hybrid_program(
635 machine: &super::machine::Machine,
636 map: &super::physical::PhysicalMap,
637 compaction: &super::physical::Compaction,
638 set: &super::rtl::column::ColumnSet,
639 weights: &mut dyn crate::model::reference::WeightSource,
640 dir: &std::path::Path,
641) -> std::io::Result<EngineProgram> {
642 hybrid_program(machine, map, compaction, set, weights, false, 1, dir)
643}
644
645pub fn write_greedy_program(
650 machine: &super::machine::Machine,
651 map: &super::physical::PhysicalMap,
652 compaction: &super::physical::Compaction,
653 set: &super::rtl::column::ColumnSet,
654 weights: &mut dyn crate::model::reference::WeightSource,
655 dir: &std::path::Path,
656) -> std::io::Result<EngineProgram> {
657 hybrid_program(machine, map, compaction, set, weights, true, 1, dir)
658}
659
660#[allow(clippy::too_many_arguments)]
665pub fn write_contexts_program(
666 machine: &super::machine::Machine,
667 map: &super::physical::PhysicalMap,
668 compaction: &super::physical::Compaction,
669 set: &super::rtl::column::ColumnSet,
670 weights: &mut dyn crate::model::reference::WeightSource,
671 greedy: bool,
672 contexts: usize,
673 dir: &std::path::Path,
674) -> std::io::Result<EngineProgram> {
675 let sequences = machine.layout.sequences;
676 if contexts < 1 || sequences % contexts != 0 {
677 return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, format!("{sequences} sequences over {contexts} contexts")));
678 }
679 hybrid_program(machine, map, compaction, set, weights, greedy, contexts, dir)
680}
681
682#[allow(clippy::too_many_arguments)]
683fn hybrid_program(
684 machine: &super::machine::Machine,
685 map: &super::physical::PhysicalMap,
686 compaction: &super::physical::Compaction,
687 set: &super::rtl::column::ColumnSet,
688 weights: &mut dyn crate::model::reference::WeightSource,
689 greedy: bool,
690 contexts: usize,
691 dir: &std::path::Path,
692) -> std::io::Result<EngineProgram> {
693 use super::rtl::streaming::StreamEntry;
694 assert_eq!(map.tensors_end, 0, "the hybrid engine's card port reads the flat space from logical line 0");
695 let invalid = |e: String| std::io::Error::new(std::io::ErrorKind::InvalidInput, e);
696 let head = &machine.layout.tensors[head_tensor(&machine.jobs)].name;
697 let streamed = map.image.tensors.iter().position(|t| &t.name == head);
698 if greedy && streamed.is_none() {
699 return Err(invalid(format!("a greedy head streams its matrix, and `{head}` is not streamed")));
700 }
701 let folds = folds_of(machine).map_err(invalid)?;
702 let entries = (0..map.image.tensors.len())
703 .map(|k| {
704 let fold = folds.get(map.image.tensors[k].name.as_str()).copied().unwrap_or(super::machine::Fold::Put);
705 StreamEntry::of(&map.image, k, set).map(|e| StreamEntry { greedy: greedy && Some(k) == streamed, fold, ..e })
706 })
707 .collect::<Result<Vec<_>, String>>()
708 .map_err(invalid)?;
709 let folding = folds.values().any(|&f| f != super::machine::Fold::Put);
710 let normed = machine.jobs.iter().any(|j| matches!(j, super::machine::Job::Delta { norm: Some(_), .. }));
711 let image = map.image.fill(weights)?;
712 let channels = map.channel_bytes(&compaction.space(machine.memory(), &entries), &image);
713 let pairs = map.image.layout.stripe.expect("a hybrid map's image laid in stripes");
714 let streaming = format!(
715 "{}/{pairs}{}{}{}",
716 set.signature(),
717 if folding { "+folds" } else { "" },
718 if normed { "+norm" } else { "" },
719 super::rtl::engine::behind(Some(machine.layout.numerics.flush))
720 );
721 write_program(machine, map, &|at| compaction.find(at), &channels, streaming, greedy, contexts, dir)
722}
723
724fn folds_of(machine: &super::machine::Machine) -> Result<std::collections::HashMap<&str, super::machine::Fold>, String> {
727 use super::machine::Job;
728 let mut folds = std::collections::HashMap::new();
729 for job in &machine.jobs {
730 if let Job::Matvec { tensor, fold, .. } = job {
731 let name = machine.layout.tensors[*tensor].name.as_str();
732 if folds.insert(name, *fold).is_some_and(|f| f != *fold) {
733 return Err(format!("`{name}` folded two ways by two jobs"));
734 }
735 }
736 }
737 Ok(folds)
738}
739
740fn head_tensor(jobs: &[super::machine::Job]) -> usize {
742 use super::machine::Job;
743 use super::program::Slot;
744 jobs.iter()
745 .rev()
746 .find_map(|j| match j {
747 Job::Matvec { tensor, to: Slot::Logits, .. } => Some(*tensor),
748 _ => None,
749 })
750 .expect("a head writes the logits")
751}
752
753#[allow(clippy::too_many_arguments)]
759fn write_program(
760 machine: &super::machine::Machine,
761 map: &super::physical::PhysicalMap,
762 place: &dyn Fn(u64) -> Option<u64>,
763 channels: &[Vec<u8>],
764 streaming: String,
765 greedy: bool,
766 contexts: usize,
767 dir: &std::path::Path,
768) -> std::io::Result<EngineProgram> {
769 use super::machine::{BlockState, Job, Row, StoreLayout, LINE};
770 use super::program::Slot;
771 let layout = &machine.layout;
772 let jobs = &machine.jobs;
773 let Some(Job::Embed { tensor: embed }) = jobs.first() else { panic!("a step opens with the host's embedding") };
774 assert!(map.granule.is_power_of_two(), "the card port's granule is a power of two");
775 let rows = layout.sequences / contexts;
776 let store = if greedy { StoreLayout::greedy(jobs, layout, rows) } else { StoreLayout::of(jobs, layout, rows) };
777 let t = super::rtl::engine::tables_at(layout, &store, &jobs[1..], &[Row { sequence: 0, position: 0, token: 0, ring: 0 }], place);
778 std::fs::create_dir_all(dir)?;
779 let write = |name: &str, lines: Vec<[u8; 64]>| std::fs::write(dir.join(name), lines.concat());
780 write("table0.bin", t.jobs.iter().map(|w| host_line(&w.to_le_bytes())).collect())?;
781 write("table2.bin", t.slots.iter().map(|w| host_line(&w.to_le_bytes())).collect())?;
782 write("table3.bin", t.tensors.iter().map(|w| host_line(&w.to_le_bytes())).collect())?;
783 write("table4.bin", t.ropes.iter().map(|w| host_line(&w.to_le_bytes())).collect())?;
784 write("table5.bin", t.blocks.iter().map(|f| host_line(&f.iter().flat_map(|x| x.to_le_bytes()).collect::<Vec<u8>>())).collect())?;
785 std::fs::write(dir.join("jobs.txt"), job_listing(&jobs[1..]))?;
786 for (k, channel) in channels.iter().enumerate() {
787 std::fs::write(dir.join(format!("channel{k}.bin")), channel)?;
788 }
789 let e = &layout.tensors[*embed];
790 let (hidden, tokens) = (e.dims[0] as usize, e.dims[1] as usize);
791 let rb = e.row_bytes() as usize;
792 let mut table = Vec::with_capacity(tokens * hidden * 4);
793 for token in 0..tokens {
794 let at = e.at as usize + token * rb;
795 let mut v = Vec::new();
796 e.ty.dequantize(&machine.memory()[at..at + rb], &mut v);
797 table.extend(v.iter().flat_map(|x| x.to_le_bytes()));
798 }
799 std::fs::write(dir.join("embed.bin"), table)?;
800 let vocab = layout.tensors[head_tensor(jobs)].dims[1] as usize;
801 let ring_rows = layout
802 .blocks
803 .iter()
804 .find_map(|b| match b {
805 BlockState::Delta { taps, .. } => Some(taps.saturating_sub(1).max(1)),
806 _ => None,
807 })
808 .unwrap_or(1);
809 assert!(layout.regions % LINE == 0 && layout.region % LINE == 0, "regions on lines");
810 let regions = place(layout.regions).expect("a place for the regions");
811 let p = EngineProgram {
812 knobs: t.state_f16 as u8 | (t.cache_int8 as u8) << 1 | (t.two_words as u8) << 2 | (t.per_block as u8) << 3,
813 space: map.tensors_end / LINE,
814 base: map.base,
815 granule_log2: map.granule.trailing_zeros(),
816 job_words: t.jobs.len(),
817 residual: store.slots[Slot::Residual.code() as usize],
818 logits: store.slots[Slot::Logits.code() as usize],
819 greedy,
820 store_beats: store.beats,
821 tokens,
822 hidden,
823 vocab,
824 ring_rows,
825 regions: regions / LINE,
826 region: layout.region / LINE,
827 sequences: layout.sequences,
828 ctx: layout.ctx,
829 channel_lines: map.channel_lines.clone(),
830 streaming,
831 contexts,
832 fp_unit: layout.numerics.fp_unit,
833 };
834 let lines: Vec<String> = p.channel_lines.iter().map(u64::to_string).collect();
835 let text = format!(
836 "knobs {}\nspace {}\nbase {}\ngranule_log2 {}\njob_words {}\nresidual {} {}\nlogits {} {}\ntokens {}\nhidden {}\n\
837 vocab {}\nring_rows {}\nregions {}\nregion {}\nsequences {}\nctx {}\nchannels {}\nstore {}\n",
838 p.knobs,
839 p.space,
840 p.base,
841 p.granule_log2,
842 p.job_words,
843 p.residual.0,
844 p.residual.1,
845 p.logits.0,
846 p.logits.1,
847 p.tokens,
848 p.hidden,
849 p.vocab,
850 p.ring_rows,
851 p.regions,
852 p.region,
853 p.sequences,
854 p.ctx,
855 lines.join(" "),
856 p.store_beats
857 );
858 let text = if p.streaming.is_empty() { text } else { format!("{text}streaming {}\n", p.streaming) };
859 let text = if p.greedy { format!("{text}greedy 1\n") } else { text };
860 let text = if p.contexts > 1 { format!("{text}contexts {}\n", p.contexts) } else { text };
861 let text = if p.fp_unit == super::numerics::FpUnit::Hard { format!("{text}fp hard\n") } else { text };
862 std::fs::write(dir.join("engine.txt"), text)?;
863 let (source, _, _) = machine.identity.cut.write(&machine.spec, "");
864 assert_eq!(
865 crate::model::program::Identity::of(machine.identity.cut, &source),
866 machine.identity,
867 "the machine's step is read off the program its identity names"
868 );
869 std::fs::write(dir.join("program.lg"), source)?;
870 std::fs::write(dir.join("program.txt"), format!("{}\n", machine.identity.line()))?;
871 Ok(p)
872}
873
874pub const STATUS_OK: u8 = 0x00;
876pub const STATUS_RUNNING: u8 = 0x01;
879pub const STATUS_UNKNOWN_OP: u8 = 0x80;
880pub const ERROR_COMMAND_DROPPED: u64 = 1;
882
883pub const CLOCK_DOMAINS_FILE: &str = "hw/logos_d5005_domains.json";
885
886#[derive(Clone, Debug, PartialEq, Eq)]
888pub struct AfuFile {
889 pub path: &'static str,
890 pub text: String,
891}
892
893pub fn afu_sources(cfg: &AfuConfig) -> Vec<AfuFile> {
895 assert!(cfg.compute_divide == 1 || (cfg.compute_divide >= 2 && cfg.compute_divide % 2 == 0), "a compute clock divided by {}", cfg.compute_divide);
896 assert!(cfg.max_burst.is_power_of_two() && cfg.max_burst <= 1 << (BANK_BURST_BITS - 1), "{} lines a burst", cfg.max_burst);
897 assert!(1u32 << cfg.read_fifo_log2 >= 2 * cfg.max_burst, "a read-data FIFO holds two bursts");
898 if cfg.engine.is_some() {
899 assert!(cfg.compute_divide >= ENGINE_MIN_DIVIDE, "an engine's clock divided by {}, under {ENGINE_MIN_DIVIDE}", cfg.compute_divide);
900 }
901 if cfg.hybrid() {
902 assert!(!cfg.line.secded(), "a hybrid engine's streaming unit reads lines without SECDED verdicts");
903 }
904 let file = |path, text| AfuFile { path, text };
905 let mut files = vec![
906 file("hw/rtl/sources.txt", sources_txt(cfg)),
907 file("hw/rtl/logos_d5005.json", afu_json(cfg)),
908 file("hw/rtl/ofs_plat_afu.sv", ofs_plat_afu(cfg)),
909 file("hw/rtl/logos_d5005_shell.v", shell_text(cfg.engine.as_ref(), cfg.compute_divide, cfg.contexts())),
910 file("hw/rtl/logos_dcfifo.v", dcfifo_verilog()),
911 file("hw/rtl/logos_secded.v", secded_verilog()),
912 file("hw/rtl/logos_stream.v", STREAM.to_string()),
913 file("hw/rtl/logos_csr.v", csr_verilog()),
914 file("hw/rtl/logos_bringup_core.v", core_contexts(&core_verilog(), cfg.contexts())),
915 file("hw/rtl/logos_d5005.sdc", sdc_text(cfg, &shell_text(cfg.engine.as_ref(), cfg.compute_divide, cfg.contexts()))),
916 file(CLOCK_DOMAINS_FILE, clock_domains_json(cfg)),
917 file("sw/logos_d5005.h", c_header(cfg)),
918 file("sw/logos_hal.h", HAL_H.to_string()),
919 file("sw/logos_hal_opae.c", HAL_OPAE.to_string()),
920 file("sw/logos_hal_pci.c", HAL_PCI.to_string()),
921 file("sw/logos_core.h", CORE_H.to_string()),
922 file("sw/logos_receipt.c", RECEIPT.to_string()),
923 file("sw/logos_load.c", LOAD.to_string()),
924 file("sim/logos_hal_sim.h", HAL_SIM.to_string()),
925 file("sim/logos_hal_vpi.c", HAL_VPI.to_string()),
926 file("sim/logos_hal_dpi.c", HAL_DPI.to_string()),
927 file("sim/logos_d5005_cosim.v", cosim_verilog(cfg)),
928 file("sim/logos_avmm_memory_model.v", MEMORY_MODEL.to_string()),
929 file("sim/logos_ddr4_channel_model.v", verilog_model(&Ddr4Grade::D5005_EMIF.at(), LOOKAHEAD)),
930 ];
931 if cfg.compute_divide > 1 {
932 files.push(file("hw/rtl/logos_compute_clock.v", COMPUTE_CLOCK_VERILOG.to_string()));
933 }
934 if let Some(plan) = &cfg.floorplan {
935 files.push(file("hw/rtl/logos_floorplan.tcl", plan.clone()));
936 }
937 if let Some(p) = &cfg.engine {
938 assert_eq!(cfg.line.bits(), 512, "the engine's card port moves lines of 64 data bytes");
939 files.push(file("hw/rtl/logos_card_port.v", card_port_verilog()));
940 files.push(file("hw/rtl/logos_settings.tcl", ENGINE_SETTINGS.to_string()));
941 let engine = engine_verilog(p);
942 files.push(file("sim/logos_engine_gated.v", simulation_twin(&engine)));
943 files.push(file("hw/rtl/logos_engine.v", engine));
944 if let Some(hard) = p.streaming.as_ref().and_then(|s| s.hard_float) {
945 files.push(file("sim/logos_fp_mac_model.v", hard_float_model(&hard)));
946 }
947 files.push(file("sw/logos_step.c", STEP.to_string()));
948 }
949 files
950}
951
952fn shell_text(engine: Option<&EngineParams>, divide: u32, contexts: usize) -> String {
956 let text = SHELL
957 .replace("/*ENGINE_PARAM*/", &format!(" localparam ENGINE = {};", engine.is_some() as u32))
958 .replace("/*COMPUTE*/", &compute_step(divide))
959 .replace("/*ENGINE*/", if engine.is_some() { SHELL_ENGINE } else { SHELL_NO_ENGINE });
960 let text = if contexts > 1 {
961 assert_eq!(text.matches(" wire [15:0] eng_rows;\n").count(), 1, "the shell's engine rows");
962 text.replace(" wire [15:0] eng_rows;\n", &format!(" wire [{}:0] eng_rows;\n", 16 * contexts - 1))
963 } else {
964 text
965 };
966 let shell = match engine {
967 Some(p) if p.streaming.is_some() => fast_channels(&engine_shell(&text), p.streaming.as_ref().is_some_and(|s| s.recurrence)),
968 Some(_) => engine_shell(&text),
969 None => text,
970 };
971 if engine.and_then(|p| p.streaming.as_ref()).is_some_and(|s| s.set.pe_pairs) {
972 pe_pairs_shell(&shell)
973 } else {
974 shell
975 }
976}
977
978fn pe_pairs_shell(text: &str) -> String {
981 edited(
982 text,
983 &[
984 (
985 " input uclk,\n input uclk_rst_n,\n",
986 " input uclk,\n input uclk_rst_n,\n input uclk2, // twice uclk, their rising edges together\n",
987 ),
988 (".fclk(uclk), .frst(frst),", ".fclk(uclk), .clk2(uclk2), .frst(frst),"),
989 ],
990 )
991}
992
993pub fn hybrid_shell_verilog(recurrence: bool) -> String {
996 let text = SHELL
997 .replace("/*ENGINE_PARAM*/", " localparam ENGINE = 1;")
998 .replace("/*COMPUTE*/", &compute_step(ENGINE_COMPUTE_DIVIDE))
999 .replace("/*ENGINE*/", SHELL_ENGINE);
1000 fast_channels(&engine_shell(&text), recurrence)
1001}
1002
1003fn compute_step(divide: u32) -> String {
1009 if divide == 1 {
1010 return " // The compute side steps every user cycle, from the user clock's reset.\n wire cce = 1'b1;\n wire crst_n = uclk_rst_n;".to_string();
1011 }
1012 format!(
1013 " // The compute side's step: the user cycles that end with the rising edges of the user clock divided by {divide},\n \
1014 // those that end with its falling edges, and the compute side's reset, released at the second rising edge; the\n \
1015 // divided clock's level (cphase) clocks and feeds nothing.\n wire cce, fmid, crst_n, cphase;\n \
1016 logos_compute_clock #(.DIVIDE({divide}), .LEAD({MARK_LEAD})) compute_clock(.clk(uclk), .rst_n(uclk_rst_n), .cclk(cphase),\n \
1017 .cclk_rst_n(crst_n), .fedge(cce), .fmid(fmid));"
1018 )
1019}
1020
1021pub const MARK_LEAD: u32 = 2;
1025
1026fn edited(text: &str, edits: &[(&str, &str)]) -> String {
1028 let mut out = text.to_string();
1029 for (from, to) in edits {
1030 assert_eq!(out.matches(from).count(), 1, "the shell's `{from}`");
1031 out = out.replace(from, to);
1032 }
1033 out
1034}
1035
1036fn engine_shell(text: &str) -> String {
1040 edited(
1041 text,
1042 &[
1043 (" logos_engine engine(", FAST_RESET),
1044 (" .mem_wr_ready(e_wr_ready));\n", " .mem_wr_ready(e_wr_ready), .fclk(uclk), .frst(frst), .fedge(cce), .fmid(fmid));\n"),
1045 ],
1046 )
1047}
1048
1049const FAST_RESET: &str = " // The engine's user-clock side's reset, the user clock's two clocks on; Quartus copies the last register a copy each\n \
1052 // 256 of its loads, each placed beside them.\n reg frst_a;\n (* maxfan = 256 *) reg frst;\n \
1053 always @(posedge uclk) begin frst_a <= !uclk_rst_n; frst <= frst_a; end\n logos_engine engine(";
1054
1055fn fast_channels(text: &str, recurrence: bool) -> String {
1061 let edits: [(&str, &str); 8] = [
1062 (
1063 "// a whole run of bursts the channel has in flight. The FIFOs' sides toward the core step at its step (uce).\n",
1064 "// a whole run of bursts the channel has in flight. The FIFOs' sides toward the core step at its step (uce). A hybrid\n\
1065 // engine's streaming unit reads the channel through a fast port: FIFOs of its own in the fast clock, every cycle\n\
1066 // of it, the stream engine serving one side at a time, turning only idle.\n",
1067 ),
1068 (
1069 " input mdone_ready,\n",
1070 " input mdone_ready,\n input fclk,\n input fclk_rst_n,\n input fd_valid,\n \
1071 output fd_ready,\n input [AW+LW:0] fd_data,\n output fr_valid,\n input fr_ready,\n \
1072 output [DWL-1:0] fr_data,\n",
1073 ),
1074 (
1075 " logos_dcfifo_mlab #(.W(DLW), .AW(2), .HOLDS(1)) desc_fifo(",
1076 " // The core's side of the stream engine's descriptors and lines, and the fast port's.\n \
1077 wire sd_valid, sd_ready, fq_valid, fq_ready, sr_push, fr_push, fr_wready;\n wire [DLW-1:0] sd_data, fq_data;\n \
1078 wire [RAW:0] sr_room, fr_room, fr_used;\n wire [2:0] fd_wfree, fd_rused;\n \
1079 logos_dcfifo_mlab #(.W(DLW), .AW(2), .HOLDS(1)) desc_fifo(",
1080 ),
1081 (".rvalid(d_valid),\n .rready(d_ready), .rdata(d_data), .rused(d_rused));", ".rvalid(sd_valid),\n .rready(sd_ready), .rdata(sd_data), .rused(d_rused));"),
1082 (
1083 "read_fifo(.wclk(mem_clk), .wce(1'b1), .wrst_n(mem_rst_n), .wvalid(r_push),\n .wready(r_wready), .wdata(r_word), .wfree(r_room),",
1084 "read_fifo(.wclk(mem_clk), .wce(1'b1), .wrst_n(mem_rst_n), .wvalid(sr_push),\n .wready(r_wready), .wdata(r_word), .wfree(sr_room),",
1085 ),
1086 (
1087 " wire [RAW:0] owed;\n wire busy;\n",
1088 r#" wire [RAW:0] owed;
1089 wire busy;
1090 // The fast port: its descriptors and lines through FIFOs of their own; the side the stream engine serves. The
1091 // descriptors' writer is the channel multiplexer, which may offer another port's descriptor at the step after one is
1092 // refused, the refused one still held at its port: a writer that holds no one word, so its FIFO says neither.
1093 logos_dcfifo_mlab #(.W(DLW), .AW(2)) fast_desc_fifo(.wclk(fclk), .wce(1'b1), .wrst_n(fclk_rst_n), .wvalid(fd_valid),
1094 .wready(fd_ready), .wdata(fd_data), .wfree(fd_wfree), .rclk(mem_clk), .rce(1'b1), .rrst_n(mem_rst_n), .rvalid(fq_valid),
1095 .rready(fq_ready), .rdata(fq_data), .rused(fd_rused));
1096 logos_dcfifo_m20k #(.W(DWL), .AW(RAW), .HOLDS(1)) fast_read_fifo(.wclk(mem_clk), .wce(1'b1), .wrst_n(mem_rst_n), .wvalid(fr_push),
1097 .wready(fr_wready), .wdata(r_data), .wfree(fr_room), .rclk(fclk), .rce(1'b1), .rrst_n(fclk_rst_n), .rvalid(fr_valid),
1098 .rready(fr_ready), .rdata(fr_data), .rused(fr_used));
1099 reg fast;
1100 always @(posedge mem_clk or negedge mem_rst_n)
1101 if (!mem_rst_n) fast <= 1'b0;
1102 else if (!busy && !(fast ? fq_valid : sd_valid) && (fast ? sd_valid : fq_valid)) fast <= !fast;
1103 assign d_valid = fast ? fq_valid : sd_valid;
1104 assign d_data = fast ? fq_data : sd_data;
1105 assign sd_ready = d_ready && !fast;
1106 assign fq_ready = d_ready && fast;
1107 assign sr_push = r_push && !fast;
1108 assign fr_push = r_push && fast;
1109 assign r_room = fast ? fr_room : sr_room;
1110"#,
1111 ),
1112 (
1113 " wire [NB*21-1:0] mw_flip;\n\n genvar b;\n",
1114 " wire [NB*21-1:0] mw_flip;\n // Every channel's fast port, the engine's streaming unit's.\n wire [NB-1:0] f_md_valid, f_md_ready, f_mr_valid, f_mr_ready;\n \
1115 wire [NB*DLW-1:0] f_md_data;\n wire [NB*DWL-1:0] f_mr_data;\n\n genvar b;\n",
1116 ),
1117 (
1118 " .mdone_valid(mdone_valid[b]), .mdone_ready(mdone_ready[b]),\n",
1119 " .mdone_valid(mdone_valid[b]), .mdone_ready(mdone_ready[b]),\n .fclk(uclk), .fclk_rst_n(uclk_rst_n), .fd_valid(f_md_valid[b]), \
1120 .fd_ready(f_md_ready[b]),\n .fd_data(f_md_data[b*DLW +: DLW]), .fr_valid(f_mr_valid[b]), .fr_ready(f_mr_ready[b]),\n \
1121 .fr_data(f_mr_data[b*DWL +: DWL]),\n",
1122 ),
1123 ];
1124 let out = edited(text, &edits);
1125 let out = edited(
1126 &out,
1127 &[(
1128 " .mem_wr_ready(e_wr_ready), .fclk(uclk), .frst(frst), .fedge(cce), .fmid(fmid));\n",
1129 " .mem_wr_ready(e_wr_ready), .fclk(uclk), .frst(frst), .fedge(cce), .fmid(fmid), .sm_md_valid(f_md_valid),\n \
1130 .sm_md_ready(f_md_ready), .sm_md_data(f_md_data), .sm_mr_valid(f_mr_valid), .sm_mr_ready(f_mr_ready), .sm_mr_data(f_mr_data));\n",
1131 )],
1132 );
1133 if recurrence {
1134 fast_writes(&out)
1135 } else {
1136 out
1137 }
1138}
1139
1140fn fast_writes(text: &str) -> String {
1145 edited(
1146 text,
1147 &[
1148 (
1149 "// of it, the stream engine serving one side at a time, turning only idle.\n",
1150 "// of it, the stream engine serving one side at a time, turning only idle. Its recurrence unit writes the channel\n\
1151 // through the fast port too, a write's done token back to the side that wrote.\n",
1152 ),
1153 (
1154 " output [DWL-1:0] fr_data,\n",
1155 " output [DWL-1:0] fr_data,\n input fw_valid,\n output fw_ready,\n input [DWL-1:0] fw_data,\n \
1156 output fdone_valid,\n input fdone_ready,\n",
1157 ),
1158 (".wvalid(done),\n .wready(done_wready)", ".wvalid(done_core),\n .wready(done_wready)"),
1159 (" wire r_wready, done_wready, done_token;\n", " wire r_wready, done_wready, done_token, done_core;\n"),
1160 (
1161 ".w_valid(w_valid), .w_ready(w_ready),\n .w_data(w_data), .w_flip(w_flip),",
1162 ".w_valid(e_w_valid), .w_ready(e_w_ready),\n .w_data(e_w_data), .w_flip(e_w_flip),",
1163 ),
1164 (" assign r_room = fast ? fr_room : sr_room;\n", FAST_WRITES),
1165 (
1166 " wire [NB*DLW-1:0] f_md_data;\n wire [NB*DWL-1:0] f_mr_data;\n",
1167 " wire [NB*DLW-1:0] f_md_data;\n wire [NB*DWL-1:0] f_mr_data;\n wire [NB-1:0] f_mw_valid, f_mw_ready, f_mdone_valid, f_mdone_ready;\n \
1168 wire [NB*DWL-1:0] f_mw_data;\n",
1169 ),
1170 (
1171 " .fr_data(f_mr_data[b*DWL +: DWL]),\n",
1172 " .fr_data(f_mr_data[b*DWL +: DWL]), .fw_valid(f_mw_valid[b]), .fw_ready(f_mw_ready[b]),\n \
1173 .fw_data(f_mw_data[b*DWL +: DWL]), .fdone_valid(f_mdone_valid[b]), .fdone_ready(f_mdone_ready[b]),\n",
1174 ),
1175 (
1176 ".sm_mr_valid(f_mr_valid), .sm_mr_ready(f_mr_ready), .sm_mr_data(f_mr_data));\n",
1177 ".sm_mr_valid(f_mr_valid), .sm_mr_ready(f_mr_ready), .sm_mr_data(f_mr_data),\n \
1178 .sm_mw_valid(f_mw_valid), .sm_mw_ready(f_mw_ready), .sm_mw_data(f_mw_data), .sm_mdone_valid(f_mdone_valid),\n \
1179 .sm_mdone_ready(f_mdone_ready), .map_space(port_space), .map_base(port_base), .map_granule_log2(port_granule_log2));\n",
1180 ),
1181 ],
1182 )
1183}
1184
1185const FAST_WRITES: &str = r#" assign r_room = fast ? fr_room : sr_room;
1187 // The fast port's lines to write through a FIFO of their own and its writes' done tokens back through another: the
1188 // stream engine's writer takes the lines of the side it serves, and a write done goes back to that side.
1189 wire fw_q_valid, fw_q_ready, fdone_wready, fdone_token, e_w_valid, e_w_ready;
1190 wire [DWL-1:0] fw_q_data, e_w_data;
1191 wire [20:0] e_w_flip;
1192 wire [WAW:0] fw_wfree, fw_rused;
1193 wire [2:0] fdone_wfree, fdone_rused;
1194 logos_dcfifo_m20k #(.W(DWL), .AW(WAW), .HOLDS(1)) fast_write_fifo(.wclk(fclk), .wce(1'b1), .wrst_n(fclk_rst_n), .wvalid(fw_valid),
1195 .wready(fw_ready), .wdata(fw_data), .wfree(fw_wfree), .rclk(mem_clk), .rce(1'b1), .rrst_n(mem_rst_n),
1196 .rvalid(fw_q_valid), .rready(fw_q_ready), .rdata(fw_q_data), .rused(fw_rused));
1197 logos_dcfifo_mlab #(.W(1), .AW(2), .TOKENS(1)) fast_done_fifo(.wclk(mem_clk), .wce(1'b1), .wrst_n(mem_rst_n), .wvalid(done && fast),
1198 .wready(fdone_wready), .wdata(1'b1), .wfree(fdone_wfree), .rclk(fclk), .rce(1'b1), .rrst_n(fclk_rst_n),
1199 .rvalid(fdone_valid), .rready(fdone_ready), .rdata(fdone_token), .rused(fdone_rused));
1200 assign e_w_valid = fast ? fw_q_valid : w_valid;
1201 assign e_w_data = fast ? fw_q_data : w_data;
1202 assign e_w_flip = fast ? 21'h0 : w_flip;
1203 assign w_ready = e_w_ready && !fast;
1204 assign fw_q_ready = e_w_ready && fast;
1205 assign done_core = done && !fast;
1206"#;
1207
1208pub fn cosim_user_clock_ps(cfg: &AfuConfig) -> u64 {
1211 (1e6 / cfg.user_clock_mhz as f64).round() as u64 / 2 * 2
1212}
1213
1214fn channel_model(model: ChannelModel) -> &'static str {
1216 match model {
1217 ChannelModel::Random => "logos_avmm_memory_model #(.AW(AW), .DW(DWM), .BW(MBW), .LINES_LOG2(20), .SEED(100 + b))",
1218 ChannelModel::Ddr4 => "logos_ddr4_channel_model #(.AW(AW), .DW(DWM), .BW(MBW), .LINES_LOG2(20), .LOG(DDR4_LOG), .ID(b))",
1219 }
1220}
1221
1222fn cosim_verilog(cfg: &AfuConfig) -> String {
1223 [
1224 ("{mem_bits}", cfg.line.bus_bits().to_string()),
1225 ("{ecc}", (cfg.line.secded() as u32).to_string()),
1226 ("{id_l}", format!("{:016x}", cfg.uuid as u64)),
1227 ("{id_h}", format!("{:016x}", (cfg.uuid >> 64) as u64)),
1228 ("{build}", format!("{:016x}", cfg.build)),
1229 ("{uclk_ps}", cosim_user_clock_ps(cfg).to_string()),
1230 ("{divide}", cfg.compute_divide.to_string()),
1231 ("{channel_model}", channel_model(cfg.channel_model).to_string()),
1232 (
1233 "{uclk2_clock}",
1234 if cfg.pe_pairs() {
1235 assert_eq!(cosim_user_clock_ps(cfg) % 4, 0, "a paired engine's twice clock takes a quarter of the user period");
1236 " // The paired processing elements' clock, twice uclk, its rising edges on uclk's and between them.\n \
1237 reg uclk2 = 1;\n always #(UCLK_PS / 4) uclk2 = ~uclk2;\n"
1238 .to_string()
1239 } else {
1240 String::new()
1241 },
1242 ),
1243 ("{uclk2_cosim}", if cfg.pe_pairs() { " .uclk2(uclk2),".to_string() } else { String::new() }),
1244 ]
1245 .iter()
1246 .fold(COSIM_TEMPLATE.to_string(), |text, (marker, value)| text.replace(marker, value))
1247}
1248
1249pub fn shell_verilog() -> Vec<(&'static str, String)> {
1251 vec![
1252 ("logos_d5005_shell.v", shell_text(None, 1, 1)),
1253 ("logos_dcfifo.v", dcfifo_verilog()),
1254 ("logos_secded.v", secded_verilog()),
1255 ("logos_stream.v", STREAM.to_string()),
1256 ("logos_csr.v", csr_verilog()),
1257 ("logos_bringup_core.v", core_verilog()),
1258 ]
1259}
1260
1261fn sources_txt(cfg: &AfuConfig) -> String {
1263 let engine: &[&str] = if cfg.engine.is_some() { &["logos_engine.v", "logos_card_port.v"] } else { &[] };
1264 let divider: &[&str] = if cfg.compute_divide > 1 { &["logos_compute_clock.v"] } else { &[] };
1265 let floorplan: &[&str] = if cfg.floorplan.is_some() { &["QI:logos_floorplan.tcl"] } else { &[] };
1266 let settings: &[&str] = if cfg.engine.is_some() { &["QI:logos_settings.tcl"] } else { &[] };
1267 let exercisers = format!("QI:{FIM_STATIC_EXERCISERS}");
1268 let flat: &[&str] = if cfg.flat { &[exercisers.as_str()] } else { &[] };
1269 ["logos_d5005.json", "logos_dcfifo.v", "logos_secded.v", "logos_stream.v", "logos_csr.v", "logos_bringup_core.v"]
1270 .iter()
1271 .chain(engine)
1272 .chain(divider)
1273 .chain(&["logos_d5005_shell.v", "ofs_plat_afu.sv", "logos_d5005.sdc"])
1274 .chain(settings)
1275 .chain(floorplan)
1276 .chain(flat)
1277 .map(|f| format!("{f}\n"))
1278 .collect()
1279}
1280
1281pub const ENGINE_SETTINGS: &str = "# The engine's MLABs read without a register after them: an MLAB's write and a read of the same word timed, so a word
1287# read in the clock after its write is the one written, as the RTL reads it.
1288set_global_assignment -name MLAB_ADD_TIMING_CONSTRAINTS_FOR_MIXED_PORT_FEED_THROUGH_MODE_SETTING_DONT_CARE ON
1289";
1290
1291fn sdc_text(cfg: &AfuConfig, shell: &str) -> String {
1294 let step = if cfg.compute_divide > 1 { compute_step_sdc(cfg.compute_divide, shell) } else { String::new() };
1295 if cfg.engine.is_none() {
1296 return format!("{SDC}{step}");
1297 }
1298 let d = cfg.compute_divide;
1299 let engine = [("{half}", d / 2 - 1), ("{half_hold}", d / 2 - 2), ("{held}", d / 2 - 2), ("{held_hold}", d / 2 - 3)]
1300 .iter()
1301 .fold(ENGINE_SDC.to_string(), |t, (m, v)| t.replace(m, &v.to_string()));
1302 format!("{SDC}{step}{engine}")
1303}
1304
1305pub fn compute_fifo_sides(shell: &str) -> Vec<(String, char)> {
1309 let mut sides = Vec::new();
1310 for (at, _) in shell.match_indices("logos_dcfifo_") {
1311 let text = &shell[at..];
1312 let text = &text[..text.find(");").unwrap_or(text.len())];
1313 let Some(open) = text.find(") ") else { continue };
1314 let name: String = text[open + 2..].chars().take_while(|c| c.is_ascii_alphanumeric() || *c == '_').collect();
1315 let port = |p: &str| text.find(&format!(".{p}(")).map(|i| text[i + p.len() + 2..].split(')').next().unwrap_or("").trim().to_string());
1316 let compute = |clk: &str, ce: &str| port(clk).as_deref() == Some("uclk") && matches!(port(ce).as_deref(), Some("cce") | Some("uce"));
1317 if name.is_empty() {
1318 continue;
1319 }
1320 if compute("wclk", "wce") {
1321 sides.push((name.clone(), 'w'));
1322 }
1323 if compute("rclk", "rce") {
1324 sides.push((name, 'r'));
1325 }
1326 }
1327 sides.sort();
1328 sides.dedup();
1329 sides
1330}
1331
1332pub const FIFO_SIDE_REGISTERS: [(char, &[&str]); 2] =
1335 [('w', &["wbin", "wgray", "rgray_s1", "rgray_s2", "rbin_w"]), ('r', &["rbin", "rgray", "wgray_s1", "wgray_s2", "wbin_r", "rdata_q", "rvalid_q"])];
1336
1337fn compute_step_sdc(divide: u32, shell: &str) -> String {
1343 let mut sides = String::new();
1344 for (name, side) in compute_fifo_sides(shell) {
1345 sides.push_str(&format!(" {name} {side}"));
1346 }
1347 let regs = |side: char| FIFO_SIDE_REGISTERS.iter().find(|(s, _)| *s == side).map_or(String::new(), |(_, r)| r.join(" "));
1348 COMPUTE_STEP_SDC
1349 .replace("{sides}", sides.trim_start())
1350 .replace("{w_regs}", ®s('w'))
1351 .replace("{r_regs}", ®s('r'))
1352 .replace("{setup}", ÷.to_string())
1353 .replace("{hold}", &(divide - 1).to_string())
1354}
1355
1356const COMPUTE_STEP_SDC: &str = r##"
1358# The shell's compute side steps only at the compute clock's step: a path between two of its registers has the step's
1359# user clocks.
1360set logos_step [get_keepers -nowarn {*|shell|core|* *|shell|port|*}]
1361foreach {name side} {{sides}} {
1362 foreach entity {logos_dcfifo_mlab logos_dcfifo_m20k} {
1363 foreach inst [get_entity_instances $entity] {
1364 if {![string match "*|$name" $inst]} { continue }
1365 set regs [expr {$side eq "w" ? {{w_regs}} : {{r_regs}}}]
1366 foreach reg $regs {
1367 set logos_step [add_to_collection $logos_step [get_keepers -nowarn $inst|$reg]]
1368 set logos_step [add_to_collection $logos_step [get_keepers -nowarn $inst|$reg[*]]]
1369 }
1370 set logos_step [add_to_collection $logos_step [get_keepers -nowarn $inst|mem*]]
1371 }
1372 }
1373}
1374if {[get_collection_size $logos_step] > 0} {
1375 set_multicycle_path -from $logos_step -to $logos_step -setup -end {setup}
1376 set_multicycle_path -from $logos_step -to $logos_step -hold -end {hold}
1377}
1378"##;
1379
1380fn clock_request(cfg: &AfuConfig) -> (String, String) {
1385 if cfg.pe_pairs() {
1386 return ((2 * cfg.user_clock_mhz).to_string(), cfg.user_clock_mhz.to_string());
1388 }
1389 if cfg.compute_divide == 1 {
1390 (format!("\"auto-{}\"", cfg.user_clock_mhz), "\"auto\"".to_string())
1391 } else {
1392 let low = if cfg.user_clock_mhz % 2 == 0 { (cfg.user_clock_mhz / 2).to_string() } else { format!("{}.5", cfg.user_clock_mhz / 2) };
1393 (cfg.user_clock_mhz.to_string(), low)
1394 }
1395}
1396
1397fn afu_json(cfg: &AfuConfig) -> String {
1398 format!(
1399 "{{\n \"version\": 1,\n \"afu-image\": {{\n \"power\": 0,\n \"clock-frequency-high\": {high},\n \
1400 \"clock-frequency-low\": {low},\n \"afu-top-interface\": {{\n \"class\": \"ofs_plat_afu\"\n }},\n \
1401 \"accelerator-clusters\": [\n {{\n \"name\": \"{name}\",\n \"total-contexts\": 1,\n \
1402 \"accelerator-type-uuid\": \"{uuid}\"\n }}\n ]\n }}\n}}\n",
1403 high = clock_request(cfg).0,
1404 low = clock_request(cfg).1,
1405 name = cfg.name,
1406 uuid = cfg.uuid_text(),
1407 )
1408}
1409
1410fn ofs_plat_afu(cfg: &AfuConfig) -> String {
1411 let lanes = match cfg.line {
1412 LocalMemoryLine::Data512 => "its data bits",
1413 LocalMemoryLine::DataAndEcc576 => "its ECC lanes carried as data",
1414 LocalMemoryLine::Data512Secded => "its ECC lanes holding each line's SECDED check bytes",
1415 };
1416 [
1417 ("{lanes}", lanes.to_string()),
1418 ("{mem_params}", cfg.line.pim_params().to_string()),
1419 ("{mem_width}", cfg.line.pim_width().to_string()),
1420 ("{ecc}", (cfg.line.secded() as u32).to_string()),
1421 ("{host_burst_bits}", BANK_BURST_BITS.to_string()),
1422 ("{max_burst}", cfg.max_burst.to_string()),
1423 ("{read_fifo_log2}", cfg.read_fifo_log2.to_string()),
1424 ("{write_fifo_log2}", cfg.write_fifo_log2.to_string()),
1425 ("{id_l}", format!("{:016x}", cfg.uuid as u64)),
1426 ("{id_h}", format!("{:016x}", (cfg.uuid >> 64) as u64)),
1427 ("{build}", format!("{:016x}", cfg.build)),
1428 ("{user_clocks}", user_clocks(cfg).to_string()),
1429 ("{uclk2}", if cfg.pe_pairs() { " .uclk2(uclk2),".to_string() } else { String::new() }),
1430 ]
1431 .iter()
1432 .fold(AFU_TEMPLATE.to_string(), |text, (marker, value)| text.replace(marker, value))
1433}
1434
1435fn user_clocks(cfg: &AfuConfig) -> &'static str {
1439 if cfg.pe_pairs() {
1440 " // The user side in uClk_usrDiv2 and the engine's paired processing elements in uClk_usr, twice it: one PLL's two\n \
1441 // outputs, their rising edges together.\n wire uclk = plat_ifc.clocks.ports[0].uClk_usrDiv2.clk;\n \
1442 wire uclk_rst_n = plat_ifc.clocks.ports[0].uClk_usrDiv2.reset_n;\n wire uclk2 = plat_ifc.clocks.ports[0].uClk_usr.clk;"
1443 } else {
1444 " wire uclk = plat_ifc.clocks.ports[0].uClk_usr.clk;\n wire uclk_rst_n = plat_ifc.clocks.ports[0].uClk_usr.reset_n;"
1445 }
1446}
1447
1448pub const COMPUTE_CLOCK_VERILOG: &str = r#"module logos_compute_clock #(parameter DIVIDE = 2, parameter LEAD = 0)
1454 (input clk, input rst_n, output reg cclk, output cclk_rst_n, output fedge, output fmid);
1455 localparam W = (DIVIDE > 2) ? $clog2(DIVIDE / 2) : 1;
1456 localparam [W-1:0] LAST = DIVIDE / 2 - 1;
1457 reg [W-1:0] count;
1458 always @(posedge clk or negedge rst_n)
1459 if (!rst_n) begin count <= {W{1'b0}}; cclk <= 1'b0; end
1460 else if (count == LAST) begin count <= {W{1'b0}}; cclk <= ~cclk; end
1461 else count <= count + 1'b1;
1462 // The marks registered, each the next cycle's, so an enable of the whole engine leaves a register.
1463 wire [W-1:0] count_next = (count == LAST) ? {W{1'b0}} : count + 1'b1;
1464 wire cclk_next = (count == LAST) ? ~cclk : cclk;
1465 (* maxfan = 2000 *) reg mark_edge, mark_mid;
1466 always @(posedge clk or negedge rst_n)
1467 if (!rst_n) begin mark_edge <= LAST == {W{1'b0}}; mark_mid <= 1'b0; end
1468 else begin mark_edge <= count_next == LAST && !cclk_next; mark_mid <= count_next == LAST && cclk_next; end
1469 reg [1:0] release_q;
1470 always @(posedge clk or negedge rst_n)
1471 if (!rst_n) release_q <= 2'b00; else if (mark_edge) release_q <= {release_q[0], 1'b1};
1472 // LEAD > 0: the marks and the reset's release LEAD user clocks on, through registers of no reset the retimer may move
1473 // along the wire and the last of which Quartus copies beside its loads, so no mark crosses the die in one clock; the
1474 // reset still asserted the moment the user clock's is. Every consumer sees the divider LEAD clocks late, alike.
1475 generate
1476 if (LEAD == 0) begin : now
1477 assign fedge = mark_edge;
1478 assign fmid = mark_mid;
1479 assign cclk_rst_n = release_q[1];
1480 end else begin : late
1481 (* maxfan = 64 *) reg [LEAD-1:0] edge_d, mid_d;
1482 reg [LEAD-1:0] release_d;
1483 always @(posedge clk) begin
1484 edge_d <= (edge_d << 1) | {{(LEAD-1){1'b0}}, mark_edge};
1485 mid_d <= (mid_d << 1) | {{(LEAD-1){1'b0}}, mark_mid};
1486 release_d <= (release_d << 1) | {{(LEAD-1){1'b0}}, release_q[1]};
1487 end
1488 assign fedge = edge_d[LEAD-1];
1489 assign fmid = mid_d[LEAD-1];
1490 assign cclk_rst_n = release_q[1] && release_d[LEAD-1];
1491 end
1492 endgenerate
1493endmodule
1494"#;
1495
1496const ENGINE_SDC: &str = r##"
1503# The engine stepping in the user clock at the compute clock's edges, and its step boundary with the shell.
1504set logos_cap [get_keepers -nowarn {*|engine|bridge|cap_*}]
1505set logos_held [get_keepers -nowarn {*|engine|bridge|held_*}]
1506set logos_core [get_keepers -nowarn {*|engine|core|*}]
1507set logos_fast [get_keepers -nowarn {*|engine|fast|*}]
1508if {[get_collection_size $logos_held] > 0} {
1509 set_multicycle_path -from $logos_held -setup -end {held}
1510 set_multicycle_path -from $logos_held -hold -end {held_hold}
1511}
1512foreach {from to} [list $logos_cap $logos_core $logos_core $logos_core $logos_core $logos_fast $logos_fast $logos_core $logos_cap $logos_fast] {
1513 if {[get_collection_size $from] > 0 && [get_collection_size $to] > 0} {
1514 set_multicycle_path -from $from -to $to -setup -end {half}
1515 set_multicycle_path -from $from -to $to -hold -end {half_hold}
1516 }
1517}
1518"##;
1519
1520fn dcfifo_verilog() -> String {
1521 [("logos_dcfifo_mlab", "MLAB"), ("logos_dcfifo_m20k", "M20K")]
1522 .iter()
1523 .map(|(name, style)| DCFIFO.replace("MODULE_NAME", name).replace("RAMSTYLE", style))
1524 .collect::<Vec<_>>()
1525 .join("\n")
1526}
1527
1528fn csr_verilog() -> String {
1529 let map: Vec<String> = REGISTERS.iter().map(|r| format!("{} = {}", r.name, r.word)).collect();
1530 CSR.replace("/*REGISTER_MAP*/", &format!("localparam [MAW-1:0] {};", map.join(", ")))
1531}
1532
1533fn core_contexts(text: &str, contexts: usize) -> String {
1537 if contexts == 1 {
1538 return text.to_string();
1539 }
1540 let rows: Vec<String> = (1..contexts).rev().map(|c| format!("cmd_data[{} +: 16]", 96 + 16 * (c - 1))).chain(["cmd_data[64 +: 16]".to_string()]).collect();
1541 let mut out = text.to_string();
1542 for (from, to) in [
1543 (" output reg [15:0] eng_rows,", format!(" output reg [{}:0] eng_rows,", 16 * contexts - 1)),
1544 (" eng_rows <= cmd_data[64 +: 16];\n", format!(" eng_rows <= {{{}}};\n", rows.join(", "))),
1545 (" tbase <= cmd_data[128 +: 32];\n", " tbase <= {cmd_data[184 +: 4], cmd_data[128 +: 28]};\n".to_string()),
1546 (" default: sbase <= cmd_data[128 +: 32];\n", " default: sbase <= {cmd_data[184 +: 4], cmd_data[128 +: 28]};\n".to_string()),
1547 (
1548 "// Any other op completes at once with code 0x80,",
1549 format!(
1550 "// With an engine of {contexts} contexts, ENGINE_CONFIG's context c's token-rows at arg0[32 + 16(c - 1) +: 16] for c >= 1,\n\
1551 // ENGINE_TABLE's and STORE_READ's context at arg1[63:56] (the engine's host address's top four bits), ENGINE_RUN\n\
1552 // starting every context with rows, its word each context's 16 bits, context 0's lowest.\n\
1553 // Any other op completes at once with code 0x80,"
1554 ),
1555 ),
1556 ] {
1557 assert_eq!(out.matches(from).count(), 1, "`{from}`");
1558 out = out.replace(from, &to);
1559 }
1560 out
1561}
1562
1563fn core_verilog() -> String {
1564 let ops: Vec<String> = Op::ALL.iter().map(|o| format!("{} = {}", o.name(), o.code())).collect();
1565 CORE.replace(
1566 "/*OPS*/",
1567 &format!(
1568 "localparam [7:0] {};\n localparam [7:0] UNKNOWN_OP = 8'h{STATUS_UNKNOWN_OP:02x}, RUNNING = 8'h{STATUS_RUNNING:02x};",
1569 ops.join(", ")
1570 ),
1571 )
1572}
1573
1574fn clock_domains_json(cfg: &AfuConfig) -> String {
1577 let p = |name: &str, clock: &str| format!(" {{\"port\": \"{name}\", \"clock\": \"{clock}\"}}");
1578 let per_bank = |name: &str, width: &str| format!(" {{\"port\": \"{name}\", \"clock\": \"mem_clk\", \"per_bank\": \"{width}\"}}");
1579 let mut rows = vec![p("pclk_rst_n", "pclk"), p("uclk_rst_n", "uclk")];
1580 for port in [
1581 "mmio_address", "mmio_read", "mmio_write", "mmio_writedata", "mmio_byteenable", "mmio_waitrequest",
1582 "mmio_readdata", "mmio_readdatavalid", "host_rd_address", "host_rd_read", "host_rd_burstcount",
1583 "host_rd_waitrequest", "host_rd_readdata", "host_rd_readdatavalid", "host_wr_address", "host_wr_write",
1584 "host_wr_burstcount", "host_wr_writedata", "host_wr_byteenable", "host_wr_waitrequest",
1585 "host_wr_writeresponsevalid",
1586 ] {
1587 rows.push(p(port, "pclk"));
1588 }
1589 for (port, width) in [
1590 ("mem_rst_n", "1"), ("mem_address", "AW"), ("mem_read", "1"), ("mem_write", "1"), ("mem_burstcount", "MBW"),
1591 ("mem_writedata", "DWM"), ("mem_byteenable", "DWM/8"), ("mem_waitrequest", "1"), ("mem_readdata", "DWM"),
1592 ("mem_readdatavalid", "1"),
1593 ] {
1594 rows.push(per_bank(port, width));
1595 }
1596 if cfg.engine.is_some() {
1597 let boxes = if cfg.hybrid() { "\"logos_engine_core_ce\", \"logos_engine_fast\"" } else { "\"logos_engine_core_ce\"" };
1600 let pair = if cfg.token_pairs() { "logos_matvec_pass_x2t" } else { "logos_matvec_pass_x2" };
1603 let (uclk2, pairs) = if cfg.pe_pairs() { (" \"uclk2\",".to_string(), format!("\n \"pairs\": [\"{pair}\"],")) } else { (String::new(), String::new()) };
1604 return format!(
1605 "{{\n \"top\": \"logos_d5005_shell\",\n \"parameters\": {{\"DWM\": {}, \"ECC\": {}}},\n \"clocks\": [\"pclk\", \"uclk\",{uclk2} \
1606 \"mem_clk\"],\n \"crossings\": [\"logos_dcfifo_mlab\", \"logos_dcfifo_m20k\", \"logos_engine_bridge\"],\n \
1607 \"single_clock\": [{boxes}],{pairs}\n \"ports\": [\n{}\n ]\n}}\n",
1608 cfg.line.bus_bits(),
1609 cfg.line.secded() as u32,
1610 rows.join(",\n")
1611 );
1612 }
1613 format!(
1614 "{{\n \"top\": \"logos_d5005_shell\",\n \"parameters\": {{\"DWM\": {}, \"ECC\": {}}},\n \"clocks\": [\"pclk\", \"uclk\", \
1615 \"mem_clk\"],\n \"crossings\": [\"logos_dcfifo_mlab\", \"logos_dcfifo_m20k\"],\n \"ports\": [\n{}\n ]\n}}\n",
1616 cfg.line.bus_bits(),
1617 cfg.line.secded() as u32,
1618 rows.join(",\n")
1619 )
1620}
1621
1622fn c_header(cfg: &AfuConfig) -> String {
1623 let mut h = String::from(
1624 "/* The LOGOS decode engine's bring-up accelerator on the Intel PAC D5005: the ID a driver opens it by, the byte\n \
1625 offsets of its MMIO registers, its commands and their status codes. */\n#ifndef LOGOS_D5005_H\n#define LOGOS_D5005_H\n\n",
1626 );
1627 h += &format!("#define LOGOS_D5005_AFU_ID \"{}\"\n\n", cfg.uuid_text());
1628 h += &format!(
1629 "#define LOGOS_D5005_BANKS {BANKS}\n#define LOGOS_D5005_MEM_LINE_BYTES {}\n#define LOGOS_D5005_HOST_LINE_BYTES {}\n\n",
1630 cfg.line.bits() / 8,
1631 HOST_LINE_BITS / 8
1632 );
1633 h += &format!(
1634 "/* Whether each channel line's words are SECDED(72,64)-coded on the bus, and the bus bit of check bit `j` of word\n \
1635 `w` (INJECT's bit numbers: data bit `i` of a line is bus bit `i`). */\n#define LOGOS_D5005_SECDED {}\n\
1636 #define LOGOS_D5005_SECDED_WORDS {SECDED_WORDS}u\n\
1637 #define LOGOS_D5005_SECDED_CHECK_BIT(w, j) ({} + 8u * ({}u - (w)) + (j))\n\n",
1638 cfg.line.secded() as u32,
1639 LocalMemoryLine::Data512Secded.bits(),
1640 SECDED_WORDS - 1
1641 );
1642 h += &format!("/* Whether the core runs the decode engine's steps (ENGINE_CONFIG, ENGINE_TABLE, ENGINE_RUN, STORE_READ). */\n#define LOGOS_D5005_ENGINE {}\n\n", cfg.engine.is_some() as u32);
1643 h += &format!("/* The divide from the user clock to the clock the core runs at. */\n#define LOGOS_D5005_COMPUTE_DIVIDE {}u\n\n", cfg.compute_divide);
1644 let (rows, store) = cfg.engine.as_ref().map_or((0, 0), |e| (e.rows, e.store_beats));
1645 h += &format!(
1646 "/* The most token-rows a step of the engine holds and its token-row store's beats: a program for more rows, or\n \
1647 whose store is larger, cannot run on it. */\n#define LOGOS_D5005_ROWS {rows}u\n#define LOGOS_D5005_STORE_BEATS {store}u\n\n"
1648 );
1649 if cfg.contexts() > 1 {
1650 h += &format!(
1651 "/* The engine's sequencer contexts, each its own token-rows (LOGOS_D5005_ROWS of them) and bank of the store: a\n \
1652 program is written for as many. */\n#define LOGOS_D5005_CONTEXTS {}u\n\n",
1653 cfg.contexts()
1654 );
1655 }
1656 if cfg.fp_hard() {
1657 h += "/* The engine's f32 products are the card's floating-point DSP blocks' (FpUnit::Hard): a program is written at\n \
1658 them, and one written at the engine's own multiply is refused. */\n#define LOGOS_D5005_FP_HARD 1\n\n";
1659 }
1660 let streaming = cfg.engine.as_ref().map_or(String::new(), |e| e.streaming_signature());
1661 h += &format!(
1662 "/* A hybrid engine's streaming unit, its column set and the row pairs a channel's stripe holds, as the programs\n \
1663 it runs name it in engine.txt; empty for an engine that streams nothing. */\n#define LOGOS_D5005_STREAMING \"{streaming}\"\n\n"
1664 );
1665 for r in REGISTERS {
1666 h += &format!("#define LOGOS_D5005_REG_{} 0x{:04x}u\n", r.name, 8 * r.word);
1667 }
1668 h += "\n";
1669 for o in Op::ALL {
1670 h += &format!("#define LOGOS_D5005_OP_{} {}u\n", o.name(), o.code());
1671 }
1672 h += &format!(
1673 "\n#define LOGOS_D5005_STATUS_OK 0x{STATUS_OK:02x}u\n#define LOGOS_D5005_STATUS_UNKNOWN_OP 0x{STATUS_UNKNOWN_OP:02x}u\n\
1674 #define LOGOS_D5005_STATUS_RUNNING 0x{STATUS_RUNNING:02x}u\n\
1675 #define LOGOS_D5005_ERROR_COMMAND_DROPPED 0x{ERROR_COMMAND_DROPPED:x}u\n\n#endif\n"
1676 );
1677 h
1678}
1679
1680const AFU_TEMPLATE: &str = r##"// The engine's boundary with the OFS platform on the D5005: the PIM's host channel as Avalon-MM MMIO and host memory
1682// in pClk, each local-memory bank as Avalon-MM in its own EMIF clock, {lanes}, and the shell behind them on the user
1683// clock, which it divides into its compute side's steps itself: every clock the platform's.
1684`include "ofs_plat_if.vh"
1685
1686module ofs_plat_afu (ofs_plat_if plat_ifc);
1687 localparam NB = local_mem_cfg_pkg::LOCAL_MEM_NUM_BANKS;
1688 localparam AW = local_mem_cfg_pkg::LOCAL_MEM_ADDR_WIDTH;
1689 localparam DWM = local_mem_cfg_pkg::{mem_width};
1690 localparam MBW = local_mem_cfg_pkg::LOCAL_MEM_BURST_CNT_WIDTH;
1691 localparam HAW = ofs_plat_host_chan_pkg::ADDR_WIDTH_LINES;
1692 localparam DWH = ofs_plat_host_chan_pkg::DATA_WIDTH;
1693 localparam HBW = {host_burst_bits};
1694
1695 wire pclk = plat_ifc.clocks.ports[0].pClk.clk;
1696 wire pclk_rst_n = plat_ifc.clocks.ports[0].pClk.reset_n;
1697{user_clocks}
1698
1699 ofs_plat_avalon_mem_rdwr_if #(`HOST_CHAN_AVALON_MEM_RDWR_PARAMS, .BURST_CNT_WIDTH(HBW)) host_mem();
1700 ofs_plat_avalon_mem_if #(`HOST_CHAN_AVALON_MMIO_PARAMS(64)) mmio64();
1701 ofs_plat_host_chan_as_avalon_mem_rdwr_with_mmio #(.ADD_CLOCK_CROSSING(0)) host_shim(
1702 .to_fiu(plat_ifc.host_chan.ports[0]),
1703 .host_mem_to_afu(host_mem),
1704 .mmio_to_afu(mmio64),
1705 .afu_clk(pclk),
1706 .afu_reset_n(pclk_rst_n));
1707
1708 ofs_plat_avalon_mem_if #(`{mem_params}) mem[NB]();
1709
1710 wire [NB-1:0] mem_clk, mem_rst_n, mem_read, mem_write, mem_waitrequest, mem_readdatavalid;
1711 wire [NB*AW-1:0] mem_address;
1712 wire [NB*MBW-1:0] mem_burstcount;
1713 wire [NB*DWM-1:0] mem_writedata, mem_readdata;
1714 wire [NB*DWM/8-1:0] mem_byteenable;
1715
1716 genvar b;
1717 for (b = 0; b < NB; b = b + 1) begin : bank
1718 ofs_plat_local_mem_as_avalon_mem #(.ADD_CLOCK_CROSSING(0)) mem_shim(
1719 .to_fiu(plat_ifc.local_mem.banks[b]),
1720 .to_afu(mem[b]),
1721 .afu_clk(uclk),
1722 .afu_reset_n(uclk_rst_n));
1723 assign mem_clk[b] = mem[b].clk;
1724 assign mem_rst_n[b] = mem[b].reset_n;
1725 assign mem[b].address = mem_address[b*AW +: AW];
1726 assign mem[b].read = mem_read[b];
1727 assign mem[b].write = mem_write[b];
1728 assign mem[b].burstcount = mem_burstcount[b*MBW +: MBW];
1729 assign mem[b].writedata = mem_writedata[b*DWM +: DWM];
1730 assign mem[b].byteenable = mem_byteenable[b*DWM/8 +: DWM/8];
1731 assign mem[b].user = '0;
1732 assign mem_waitrequest[b] = mem[b].waitrequest;
1733 assign mem_readdata[b*DWM +: DWM] = mem[b].readdata;
1734 assign mem_readdatavalid[b] = mem[b].readdatavalid;
1735 end
1736
1737 ofs_plat_if_tie_off_unused #(.HOST_CHAN_IN_USE_MASK(1), .LOCAL_MEM_IN_USE_MASK((1 << NB) - 1)) tie_off(plat_ifc);
1738
1739 assign host_mem.rd_byteenable = '1;
1740 assign host_mem.rd_user = '0;
1741 assign host_mem.wr_user = '0;
1742 assign mmio64.response = '0;
1743 assign mmio64.readresponseuser = '0;
1744 assign mmio64.writeresponsevalid = 1'b0;
1745 assign mmio64.writeresponse = '0;
1746 assign mmio64.writeresponseuser = '0;
1747
1748 logos_d5005_shell #(
1749 .NB(NB), .AW(AW), .DWM(DWM), .ECC({ecc}), .MBW(MBW), .HAW(HAW), .DWH(DWH), .HBW(HBW),
1750 .MAW(ofs_plat_host_chan_pkg::MMIO_ADDR_WIDTH_BYTES - 3),
1751 .MAXB({max_burst}), .RAW({read_fifo_log2}), .WAW({write_fifo_log2}),
1752 .AFU_ID_L(64'h{id_l}), .AFU_ID_H(64'h{id_h}), .BUILD(64'h{build})
1753 ) shell (
1754 .pclk(pclk), .pclk_rst_n(pclk_rst_n), .uclk(uclk), .uclk_rst_n(uclk_rst_n),{uclk2}
1755 .mmio_address(mmio64.address), .mmio_read(mmio64.read), .mmio_write(mmio64.write),
1756 .mmio_writedata(mmio64.writedata), .mmio_byteenable(mmio64.byteenable), .mmio_waitrequest(mmio64.waitrequest),
1757 .mmio_readdata(mmio64.readdata), .mmio_readdatavalid(mmio64.readdatavalid),
1758 .host_rd_address(host_mem.rd_address), .host_rd_read(host_mem.rd_read), .host_rd_burstcount(host_mem.rd_burstcount),
1759 .host_rd_waitrequest(host_mem.rd_waitrequest), .host_rd_readdata(host_mem.rd_readdata),
1760 .host_rd_readdatavalid(host_mem.rd_readdatavalid),
1761 .host_wr_address(host_mem.wr_address), .host_wr_write(host_mem.wr_write), .host_wr_burstcount(host_mem.wr_burstcount),
1762 .host_wr_writedata(host_mem.wr_writedata), .host_wr_byteenable(host_mem.wr_byteenable),
1763 .host_wr_waitrequest(host_mem.wr_waitrequest), .host_wr_writeresponsevalid(host_mem.wr_writeresponsevalid),
1764 .mem_clk(mem_clk), .mem_rst_n(mem_rst_n), .mem_address(mem_address), .mem_read(mem_read), .mem_write(mem_write),
1765 .mem_burstcount(mem_burstcount), .mem_writedata(mem_writedata), .mem_byteenable(mem_byteenable),
1766 .mem_waitrequest(mem_waitrequest), .mem_readdata(mem_readdata), .mem_readdatavalid(mem_readdatavalid));
1767endmodule
1768"##;
1769
1770const DCFIFO: &str = r##"// A dual-clock FIFO of 2^AW words: the only way a value crosses between two of the engine's clocks.
1771//
1772// Each side keeps a binary and a Gray pointer in its own clock. A Gray pointer changes one bit a step, so the other
1773// side samples it through two registers and sees either the old or the new count, never a mix. The RAM is written in
1774// wclk and read in rclk; a word is read only after its write pointer has crossed, two rclk edges after the write.
1775// The read side shows ahead: rvalid means rdata holds the oldest word. wfree and rused are each side's conservative
1776// view: the other side's last pointer to cross. Each side steps at its enable (wce, rce), its synchronizers too, as a
1777// side clocked that much slower does: its outputs move only at the edges it steps at.
1778//
1779// A FIFO says what its writer is: HOLDS, one that holds a refused word until it is taken (checked in simulation);
1780// TOKENS, one that gives tokens (W is 1) as pulses, counted on the write side until taken; or neither, one that may
1781// let a refused word fall.
1782module MODULE_NAME #(parameter W = 64, parameter AW = 4, parameter HOLDS = 0, parameter TOKENS = 0) (
1783 input wclk,
1784 input wce,
1785 input wrst_n,
1786 input wvalid,
1787 output wready,
1788 input [W-1:0] wdata,
1789 output [AW:0] wfree,
1790 input rclk,
1791 input rce,
1792 input rrst_n,
1793 output rvalid,
1794 input rready,
1795 output [W-1:0] rdata,
1796 output [AW:0] rused
1797);
1798 localparam [AW:0] DEPTH = 1 << AW;
1799
1800 function [AW:0] gray(input [AW:0] b);
1801 gray = b ^ (b >> 1);
1802 endfunction
1803
1804 function [AW:0] binary(input [AW:0] g);
1805 integer i;
1806 begin
1807 binary[AW] = g[AW];
1808 for (i = AW - 1; i >= 0; i = i - 1) binary[i] = binary[i + 1] ^ g[i];
1809 end
1810 endfunction
1811
1812 (* ramstyle = "RAMSTYLE, no_rw_check" *) reg [W-1:0] mem [0:DEPTH-1];
1813
1814 // Write side. Each Gray pointer stays a register of its own, the one its crossing is constrained from (its top bit
1815 // is the binary pointer's, which synthesis would otherwise merge it with); the other side's pointer, synchronized,
1816 // is converted to binary in a register, a cycle older than the synchronizer, so the free count only under-reports.
1817 reg [AW:0] wbin;
1818 (* dont_merge *) reg [AW:0] wgray;
1819 (* altera_attribute = "-name SYNCHRONIZER_IDENTIFICATION \"FORCED IF ASYNCHRONOUS\"" *) reg [AW:0] rgray_s1;
1820 (* altera_attribute = "-name SYNCHRONIZER_IDENTIFICATION \"FORCED IF ASYNCHRONOUS\"" *) reg [AW:0] rgray_s2;
1821 (* dont_merge *) reg [AW:0] rgray;
1822 reg [AW:0] rbin_w;
1823 wire full = (wbin - rbin_w) == DEPTH;
1824 // With TOKENS a write is a token given as a pulse whatever wready says: the write side counts the tokens it has not
1825 // taken and takes one a step as it has room, so none is let fall while the FIFO looks full. Otherwise a write is the
1826 // writer's word, offered until the FIFO takes it.
1827 wire offer;
1828 generate if (TOKENS) begin : tokens
1829 reg [15:0] owed;
1830 always @(posedge wclk or negedge wrst_n)
1831 if (!wrst_n) owed <= 16'd0;
1832 else if (wce) owed <= owed + {15'd0, wvalid} - {15'd0, owed != 16'd0 && !full};
1833 assign offer = owed != 16'd0;
1834 end else begin : words
1835 assign offer = wvalid;
1836 end endgenerate
1837 wire push = wce && offer && !full;
1838 assign wready = !full;
1839 assign wfree = DEPTH - (wbin - rbin_w);
1840 // With HOLDS the writer holds a word the FIFO refused until it is taken: in simulation, a refused word not offered
1841 // again at the next step, unchanged, stops the run.
1842 // synthesis translate_off
1843`ifndef SYNTHESIS
1844 reg sim_refused = 1'b0;
1845 reg [W-1:0] sim_word;
1846 always @(posedge wclk)
1847 if (!wrst_n) sim_refused <= 1'b0;
1848 else if (wce) begin
1849 if (HOLDS != 0 && sim_refused && !(wvalid && wdata === sim_word))
1850 $fatal(1, "%m: a word the FIFO refused was not offered again");
1851 sim_refused <= wvalid && full;
1852 sim_word <= wdata;
1853 end
1854`endif
1855 // synthesis translate_on
1856
1857 always @(posedge wclk) if (push) mem[wbin[AW-1:0]] <= wdata;
1858
1859 always @(posedge wclk or negedge wrst_n) begin
1860 if (!wrst_n) begin
1861 wbin <= 0;
1862 wgray <= 0;
1863 rgray_s1 <= 0;
1864 rgray_s2 <= 0;
1865 rbin_w <= 0;
1866 end else if (wce) begin
1867 rgray_s1 <= rgray;
1868 rgray_s2 <= rgray_s1;
1869 rbin_w <= binary(rgray_s2);
1870 if (push) begin
1871 wbin <= wbin + 1'b1;
1872 wgray <= gray(wbin + 1'b1);
1873 end
1874 end
1875 end
1876
1877 // Read side.
1878 reg [AW:0] rbin;
1879 (* altera_attribute = "-name SYNCHRONIZER_IDENTIFICATION \"FORCED IF ASYNCHRONOUS\"" *) reg [AW:0] wgray_s1;
1880 (* altera_attribute = "-name SYNCHRONIZER_IDENTIFICATION \"FORCED IF ASYNCHRONOUS\"" *) reg [AW:0] wgray_s2;
1881 reg [W-1:0] rdata_q;
1882 reg rvalid_q;
1883 reg [AW:0] wbin_r;
1884 wire empty = wbin_r == rbin;
1885 wire pop = rce && !empty && (!rvalid_q || rready);
1886 assign rvalid = rvalid_q;
1887 assign rdata = rdata_q;
1888 assign rused = (wbin_r - rbin) + {{AW{1'b0}}, rvalid_q};
1889
1890 always @(posedge rclk) if (pop) rdata_q <= mem[rbin[AW-1:0]];
1891
1892 always @(posedge rclk or negedge rrst_n) begin
1893 if (!rrst_n) begin
1894 rbin <= 0;
1895 rgray <= 0;
1896 wgray_s1 <= 0;
1897 wgray_s2 <= 0;
1898 wbin_r <= 0;
1899 rvalid_q <= 1'b0;
1900 end else if (rce) begin
1901 wgray_s1 <= wgray;
1902 wgray_s2 <= wgray_s1;
1903 wbin_r <= binary(wgray_s2);
1904 if (pop) begin
1905 rbin <= rbin + 1'b1;
1906 rgray <= gray(rbin + 1'b1);
1907 end
1908 if (pop) rvalid_q <= 1'b1;
1909 else if (rready) rvalid_q <= 1'b0;
1910 end
1911 end
1912endmodule
1913"##;
1914
1915const STREAM: &str = r##"// The stream engines, each in its channel's own clock: descriptors in, Avalon-MM bursts out, lines through FIFOs.
1916//
1917// A descriptor {line address, lines} moves `lines` lines in bursts of at most MAXB. A reader issues a burst only
1918// when the read-data FIFO has room for it beside every line still owed: readdatavalid cannot be held off, so the
1919// room is reserved at issue (`r_room` is the FIFO's write-side free count, which only under-reports), one request on
1920// the bus at a time so each is sized against what the last reserved. A writer takes a burst's beats from the
1921// write-data FIFO as they come; an Avalon burst may pause between beats.
1922module logos_stream_reader #(
1923 parameter AW = 27, // line address bits
1924 parameter DW = 512, // data bits a line
1925 parameter BW = 7, // burstcount bits
1926 parameter LW = 32, // line-count bits of a descriptor
1927 parameter MAXB = 64, // lines a burst, at most 2^(BW-1)
1928 parameter RW = 10 // read-FIFO count bits
1929) (
1930 input clk,
1931 input rst_n,
1932 input d_valid,
1933 output d_ready,
1934 input [AW+LW-1:0] d_data,
1935 output r_valid,
1936 output [DW-1:0] r_data,
1937 input [RW-1:0] r_room,
1938 output reg [AW-1:0] avm_address,
1939 output reg avm_read,
1940 output reg [BW-1:0] avm_burstcount,
1941 input avm_waitrequest,
1942 input [DW-1:0] avm_readdata,
1943 input avm_readdatavalid,
1944 output reg [RW-1:0] owed,
1945 output active
1946);
1947 localparam [LW-1:0] MAXL = MAXB;
1948 reg busy_q;
1949 reg [AW-1:0] next_addr;
1950 reg [LW-1:0] left;
1951 // The next burst and the room beside every line owed, each a register a cycle behind left and owed; a burst issues
1952 // only while both are current, a cycle after anything changed them. The room only grows meanwhile (the FIFO's other
1953 // side frees it; a line returning leaves the room and the lines owed together), so it never over-reports.
1954 reg [LW-1:0] chunk;
1955 reg [RW:0] spare;
1956 reg current;
1957 wire [AW+LW-1:0] chunk_wide = {{AW{1'b0}}, chunk};
1958 wire [AW-1:0] step = chunk_wide[AW-1:0];
1959 // A burst is at most MAXB lines, fewer than the read FIFO holds, so the room and the burst compare at its width.
1960 wire fits = spare >= {1'b0, chunk[RW-1:0]};
1961 wire accepted = avm_read && !avm_waitrequest;
1962 wire [RW-1:0] burst_lines = avm_burstcount;
1963 wire [RW-1:0] returned = avm_readdatavalid;
1964 assign d_ready = !busy_q;
1965 assign active = busy_q;
1966 assign r_valid = avm_readdatavalid;
1967 assign r_data = avm_readdata;
1968 always @(posedge clk or negedge rst_n) begin
1969 if (!rst_n) begin
1970 busy_q <= 1'b0;
1971 next_addr <= 0;
1972 left <= 0;
1973 avm_address <= 0;
1974 avm_read <= 1'b0;
1975 avm_burstcount <= 0;
1976 owed <= 0;
1977 chunk <= 0;
1978 spare <= 0;
1979 current <= 1'b0;
1980 end else begin
1981 owed <= owed + (accepted ? burst_lines : {RW{1'b0}}) - returned;
1982 chunk <= (left > MAXL) ? MAXL : left;
1983 spare <= {1'b0, r_room} - {1'b0, owed};
1984 current <= 1'b1;
1985 if (!busy_q) begin
1986 if (d_valid) begin
1987 busy_q <= 1'b1;
1988 next_addr <= d_data[AW+LW-1:LW];
1989 left <= d_data[LW-1:0];
1990 current <= 1'b0;
1991 end
1992 end else if (avm_read) begin
1993 if (!avm_waitrequest) begin avm_read <= 1'b0; current <= 1'b0; end
1994 end else if (left == 0) begin
1995 busy_q <= 1'b0;
1996 end else if (current && fits) begin
1997 current <= 1'b0;
1998 avm_read <= 1'b1;
1999 avm_address <= next_addr;
2000 avm_burstcount <= chunk[BW-1:0];
2001 next_addr <= next_addr + step;
2002 left <= left - chunk;
2003 end
2004 end
2005 end
2006endmodule
2007
2008module logos_stream_writer #(
2009 parameter AW = 27,
2010 parameter DW = 512,
2011 parameter BW = 7,
2012 parameter LW = 32,
2013 parameter MAXB = 64
2014) (
2015 input clk,
2016 input rst_n,
2017 input d_valid,
2018 output d_ready,
2019 input [AW+LW-1:0] d_data,
2020 input w_valid,
2021 output w_ready,
2022 input [DW-1:0] w_data,
2023 output reg [AW-1:0] avm_address,
2024 output avm_write,
2025 output reg [BW-1:0] avm_burstcount,
2026 output [DW-1:0] avm_writedata,
2027 output [DW/8-1:0] avm_byteenable,
2028 input avm_waitrequest,
2029 output active,
2030 output reg done
2031);
2032 localparam [LW-1:0] MAXL = MAXB;
2033 reg busy_q, in_burst;
2034 reg [AW-1:0] next_addr;
2035 reg [LW-1:0] left;
2036 reg [BW-1:0] beats;
2037 wire [LW-1:0] chunk = (left > MAXL) ? MAXL : left;
2038 wire [AW+LW-1:0] chunk_wide = {{AW{1'b0}}, chunk};
2039 wire [AW-1:0] step = chunk_wide[AW-1:0];
2040 wire accepted = avm_write && !avm_waitrequest;
2041 assign d_ready = !busy_q;
2042 assign active = busy_q;
2043 assign avm_write = in_burst && w_valid;
2044 assign w_ready = accepted;
2045 assign avm_writedata = w_data;
2046 assign avm_byteenable = {(DW/8){1'b1}};
2047 always @(posedge clk or negedge rst_n) begin
2048 if (!rst_n) begin
2049 busy_q <= 1'b0;
2050 in_burst <= 1'b0;
2051 next_addr <= 0;
2052 left <= 0;
2053 beats <= 0;
2054 avm_address <= 0;
2055 avm_burstcount <= 0;
2056 done <= 1'b0;
2057 end else begin
2058 done <= 1'b0;
2059 if (!busy_q) begin
2060 if (d_valid) begin
2061 busy_q <= 1'b1;
2062 next_addr <= d_data[AW+LW-1:LW];
2063 left <= d_data[LW-1:0];
2064 end
2065 end else if (in_burst) begin
2066 if (accepted) begin
2067 beats <= beats - 1'b1;
2068 if (beats == 1) in_burst <= 1'b0;
2069 end
2070 end else if (left == 0) begin
2071 busy_q <= 1'b0;
2072 done <= 1'b1;
2073 end else begin
2074 in_burst <= 1'b1;
2075 avm_address <= next_addr;
2076 avm_burstcount <= chunk[BW-1:0];
2077 beats <= chunk[BW-1:0];
2078 next_addr <= next_addr + step;
2079 left <= left - chunk;
2080 end
2081 end
2082 end
2083endmodule
2084
2085// One local-memory channel: a reader and a writer on the bank's single Avalon-MM port. A descriptor {write, address,
2086// lines} goes to the engine its op names once neither engine is issuing, so one engine drives the bus at a time;
2087// lines a read still owes keep returning while a write issues.
2088//
2089// With ECC set every line is SECDED(72,64)-coded on the bus (logos_secded.v): a line written is coded, then has the
2090// bus bits `w_flip` = {enable, b, a} names flipped (one bit when a = b); a line read is decoded in one registered
2091// stage, its words' verdicts `r_ecc` = {uncorrectable, corrected} beside it, and the line in the stage counted
2092// against the read FIFO's room as the lines the reader still owes are.
2093module logos_stream_avmm #(
2094 parameter AW = 27,
2095 parameter DW = 512, // bus bits a line
2096 parameter BW = 7,
2097 parameter LW = 32,
2098 parameter MAXB = 64,
2099 parameter RW = 10,
2100 parameter ECC = 0,
2101 parameter DL = ECC ? DW / 9 * 8 : DW // data bits a line
2102) (
2103 input clk,
2104 input rst_n,
2105 input d_valid,
2106 output d_ready,
2107 input [AW+LW:0] d_data,
2108 output r_valid,
2109 output [DL-1:0] r_data,
2110 output [15:0] r_ecc,
2111 input [RW-1:0] r_room,
2112 input w_valid,
2113 output w_ready,
2114 input [DL-1:0] w_data,
2115 input [20:0] w_flip,
2116 output [AW-1:0] avm_address,
2117 output avm_read,
2118 output avm_write,
2119 output [BW-1:0] avm_burstcount,
2120 output [DW-1:0] avm_writedata,
2121 output [DW/8-1:0] avm_byteenable,
2122 input avm_waitrequest,
2123 input [DW-1:0] avm_readdata,
2124 input avm_readdatavalid,
2125 output [RW-1:0] owed,
2126 output busy,
2127 output wdone
2128);
2129 wire is_write = d_data[AW+LW];
2130 wire rd_active, wr_active, rd_ready, wr_ready;
2131 wire idle = !rd_active && !wr_active;
2132 wire [AW-1:0] rd_address, wr_address;
2133 wire [BW-1:0] rd_burstcount, wr_burstcount;
2134 wire bus_valid;
2135 wire [DW-1:0] bus_rdata, bus_wdata;
2136 wire [RW-1:0] room;
2137 wire staged;
2138 // The bus's read data in a register on its way in, the platform's bridge register to this one. The reader counts a
2139 // line returned when it leaves this register, so a line is owed until it reaches the read FIFO, or the decoding
2140 // stage on SECDED lines, which the room counts.
2141 reg in_valid;
2142 reg [DW-1:0] in_rdata;
2143 always @(posedge clk) in_rdata <= avm_readdata;
2144 always @(posedge clk or negedge rst_n)
2145 if (!rst_n) in_valid <= 1'b0;
2146 else in_valid <= avm_readdatavalid;
2147 assign d_ready = idle;
2148 // The engines' requests in a two-deep queue on their way out to the platform's bridge: the engines see room while the
2149 // queue holds at most one, its count a register, and the bus takes from its head, so a request reaches the bus in
2150 // the order the engines made it, a read after a write behind it, and nothing in the engines waits on the bus. The
2151 // head's read and write are registers of their own, cleared as the head leaves with nothing behind it, so what the
2152 // bridge registers is a register.
2153 localparam QW = AW + BW + DW + DW / 8;
2154 wire q_read, q_write;
2155 wire [AW-1:0] q_address = wr_active ? wr_address : rd_address;
2156 wire [BW-1:0] q_burstcount = wr_active ? wr_burstcount : rd_burstcount;
2157 wire [DW-1:0] q_writedata;
2158 wire [DW/8-1:0] q_byteenable;
2159 reg [1:0] q_count;
2160 reg head_read, head_write, next_read, next_write;
2161 reg [QW-1:0] q_head, q_next;
2162 wire q_room = q_count != 2'd2;
2163 wire q_in = (q_read || q_write) && q_room;
2164 wire q_out = (head_read || head_write) && !avm_waitrequest;
2165 wire [QW-1:0] q_entry = {q_address, q_burstcount, q_writedata, q_byteenable};
2166 assign busy = !idle || owed != 0 || q_count != 2'd0 || staged;
2167 always @(posedge clk or negedge rst_n)
2168 if (!rst_n) begin
2169 q_count <= 2'd0;
2170 {head_read, head_write, next_read, next_write} <= 4'b0000;
2171 end else begin
2172 q_count <= q_count + {1'b0, q_in} - {1'b0, q_out};
2173 if (q_out) begin
2174 if (q_count == 2'd2) {head_read, head_write} <= {next_read, next_write};
2175 else if (q_in) {head_read, head_write} <= {q_read, q_write};
2176 else {head_read, head_write} <= 2'b00;
2177 end else if (q_in) begin
2178 if (q_count == 2'd0) {head_read, head_write} <= {q_read, q_write};
2179 else {next_read, next_write} <= {q_read, q_write};
2180 end
2181 end
2182 always @(posedge clk)
2183 if (q_out) begin
2184 if (q_count == 2'd2) q_head <= q_next;
2185 else if (q_in) q_head <= q_entry;
2186 end else if (q_in) begin
2187 if (q_count == 2'd0) q_head <= q_entry;
2188 else q_next <= q_entry;
2189 end
2190 assign avm_read = head_read;
2191 assign avm_write = head_write;
2192 assign {avm_address, avm_burstcount, avm_writedata, avm_byteenable} = q_head;
2193 logos_stream_reader #(.AW(AW), .DW(DW), .BW(BW), .LW(LW), .MAXB(MAXB), .RW(RW)) reader(
2194 .clk(clk), .rst_n(rst_n), .d_valid(d_valid && idle && !is_write), .d_ready(rd_ready), .d_data(d_data[AW+LW-1:0]),
2195 .r_valid(bus_valid), .r_data(bus_rdata), .r_room(room), .avm_address(rd_address), .avm_read(q_read),
2196 .avm_burstcount(rd_burstcount), .avm_waitrequest(!q_room), .avm_readdata(in_rdata),
2197 .avm_readdatavalid(in_valid), .owed(owed), .active(rd_active));
2198 logos_stream_writer #(.AW(AW), .DW(DW), .BW(BW), .LW(LW), .MAXB(MAXB)) writer(
2199 .clk(clk), .rst_n(rst_n), .d_valid(d_valid && idle && is_write), .d_ready(wr_ready), .d_data(d_data[AW+LW-1:0]),
2200 .w_valid(w_valid), .w_ready(w_ready), .w_data(bus_wdata), .avm_address(wr_address), .avm_write(q_write),
2201 .avm_burstcount(wr_burstcount), .avm_writedata(q_writedata), .avm_byteenable(q_byteenable),
2202 .avm_waitrequest(!q_room), .active(wr_active), .done(wdone));
2203 generate if (ECC) begin : secded
2204 wire [DW-1:0] one = {{(DW-1){1'b0}}, 1'b1};
2205 wire [DW-1:0] coded;
2206 wire [DL-1:0] fixed;
2207 wire [7:0] corrected, uncorrectable;
2208 reg q_valid;
2209 reg [DL-1:0] q_data;
2210 reg [15:0] q_ecc;
2211 logos_secded_encode coder(.d(w_data), .bus(coded));
2212 assign bus_wdata = coded ^ (w_flip[20] ? (one << w_flip[9:0]) | (one << w_flip[19:10]) : {DW{1'b0}});
2213 logos_secded_decode decoder(.bus(bus_rdata), .d(fixed), .corrected(corrected), .uncorrectable(uncorrectable));
2214 always @(posedge clk or negedge rst_n) begin
2215 if (!rst_n) begin
2216 q_valid <= 1'b0;
2217 q_data <= 0;
2218 q_ecc <= 0;
2219 end else begin
2220 q_valid <= bus_valid;
2221 q_data <= fixed;
2222 q_ecc <= {uncorrectable, corrected};
2223 end
2224 end
2225 assign r_valid = q_valid;
2226 assign r_data = q_data;
2227 assign r_ecc = q_ecc;
2228 assign room = r_room - {{(RW-1){1'b0}}, q_valid};
2229 assign staged = q_valid;
2230 end else begin : plain
2231 assign bus_wdata = w_data;
2232 assign r_valid = bus_valid;
2233 assign r_data = bus_rdata;
2234 assign r_ecc = 16'h0;
2235 assign room = r_room;
2236 assign staged = 1'b0;
2237 end endgenerate
2238endmodule
2239"##;
2240
2241const CSR: &str = r##"// The accelerator's registers, in the host channel's clock: the OFS feature header and ID the driver finds it by,
2242// a command doorbell into the engine, and the engine's last status record.
2243//
2244// MMIO addresses are 64-bit word indices. A write to DOORBELL pushes {arg2, arg1, arg0, doorbell} into the command
2245// FIFO in that cycle; with the FIFO full it is dropped, counted in DROPPED and ERRORS bit 0 set, so a driver reads
2246// CMD_SPACE first. Every
2247// status record the engine pushes is taken at once and mirrored. Reads answer the next cycle, in order.
2248module logos_csr #(
2249 parameter MAW = 15, // MMIO word-address bits
2250 parameter [63:0] AFU_ID_L = 64'h0,
2251 parameter [63:0] AFU_ID_H = 64'h0,
2252 parameter [3:0] AFU_MAJOR = 4'd1,
2253 parameter [3:0] AFU_MINOR = 4'd0,
2254 parameter [63:0] BUILD = 64'h0,
2255 parameter CW = 8 // command-FIFO count bits
2256) (
2257 input clk,
2258 input rst_n,
2259 input [MAW-1:0] mmio_address,
2260 input mmio_read,
2261 input mmio_write,
2262 input [63:0] mmio_writedata,
2263 input [7:0] mmio_byteenable,
2264 output mmio_waitrequest,
2265 output reg [63:0] mmio_readdata,
2266 output reg mmio_readdatavalid,
2267 // Commands, into the engine's clock.
2268 output cmd_valid,
2269 input cmd_ready,
2270 output [255:0] cmd_data,
2271 input [CW-1:0] cmd_space,
2272 // Status records {seq, status, result0, result1}, from the engine's clock.
2273 input st_valid,
2274 output st_ready,
2275 input [255:0] st_data
2276);
2277 /*REGISTER_MAP*/
2278
2279 reg [63:0] scratch, arg0, arg1, arg2, status, result0, result1, status_seq, errors, dropped;
2280 wire ring = mmio_write && mmio_address == DOORBELL;
2281
2282 assign mmio_waitrequest = 1'b0;
2283 assign cmd_valid = ring;
2284 assign cmd_data = {arg2, arg1, arg0, mmio_writedata};
2285 assign st_ready = 1'b1;
2286
2287 wire [63:0] dfh = {4'h1, 8'h0, AFU_MINOR, 7'h0, 1'b1, 24'h0, AFU_MAJOR, 12'h0};
2288
2289 function [63:0] merge(input [63:0] old, input [63:0] new_, input [7:0] be);
2290 integer i;
2291 for (i = 0; i < 8; i = i + 1) merge[8*i +: 8] = be[i] ? new_[8*i +: 8] : old[8*i +: 8];
2292 endfunction
2293
2294 always @(posedge clk or negedge rst_n) begin
2295 if (!rst_n) begin
2296 scratch <= 0; arg0 <= 0; arg1 <= 0; arg2 <= 0; dropped <= 0;
2297 status <= 0; result0 <= 0; result1 <= 0; status_seq <= 0; errors <= 0;
2298 mmio_readdata <= 0; mmio_readdatavalid <= 1'b0;
2299 end else begin
2300 if (ring && !cmd_ready) begin
2301 errors[0] <= 1'b1;
2302 dropped <= dropped + 1'b1;
2303 end
2304 if (st_valid) begin
2305 status_seq <= st_data[63:0];
2306 status <= st_data[127:64];
2307 result0 <= st_data[191:128];
2308 result1 <= st_data[255:192];
2309 end
2310 if (mmio_write) begin
2311 case (mmio_address)
2312 SCRATCH: scratch <= merge(scratch, mmio_writedata, mmio_byteenable);
2313 ARG0: arg0 <= merge(arg0, mmio_writedata, mmio_byteenable);
2314 ARG1: arg1 <= merge(arg1, mmio_writedata, mmio_byteenable);
2315 ARG2: arg2 <= merge(arg2, mmio_writedata, mmio_byteenable);
2316 ERRORS: errors <= errors & ~mmio_writedata;
2317 default: ;
2318 endcase
2319 end
2320 mmio_readdatavalid <= mmio_read;
2321 if (mmio_read) begin
2322 case (mmio_address)
2323 DFH: mmio_readdata <= dfh;
2324 ID_L: mmio_readdata <= AFU_ID_L;
2325 ID_H: mmio_readdata <= AFU_ID_H;
2326 SCRATCH: mmio_readdata <= scratch;
2327 BUILD_ID: mmio_readdata <= BUILD;
2328 STATUS: mmio_readdata <= status;
2329 RESULT0: mmio_readdata <= result0;
2330 RESULT1: mmio_readdata <= result1;
2331 STATUS_SEQ: mmio_readdata <= status_seq;
2332 CMD_SPACE: mmio_readdata <= {{(64-CW){1'b0}}, cmd_space};
2333 ERRORS: mmio_readdata <= errors;
2334 DROPPED: mmio_readdata <= dropped;
2335 ARG0: mmio_readdata <= arg0;
2336 ARG1: mmio_readdata <= arg1;
2337 ARG2: mmio_readdata <= arg2;
2338 default: mmio_readdata <= 64'h0;
2339 endcase
2340 end
2341 end
2342 end
2343endmodule
2344"##;
2345
2346const CORE: &str = r##"// The bring-up engine, in the compute clock — every register stepping in its clock at ce: fills and checks the memory
2347// channels at full rate and copies lines between host memory and a channel, so the shell's every path is exercised on
2348// the card before the decode engine takes its place behind the same ports.
2349//
2350// A command is {arg2, arg1, arg0, {tag, op}}; each completes with a status record {seq, {tag, code}, result0,
2351// result1}: seq the count of commands completed with this one, result1 the compute cycles it took. While a command
2352// runs, every 2^HB cycles in which it moved since the last, the core cuts a progress record {seq, {tag, RUNNING},
2353// {pc, progress}, cycles so far}: seq the commands completed before it, so a host waiting for the command's own record
2354// still waits, pc the engine's word (each context's in its 16 bits), and progress the low 32 bits of the count of
2355// cycles since reset in which a line crossed a memory channel (on whichever side holds the channels) or the host
2356// channel or the engine's sequencer took its next word.
2357// FILL (1): every channel in mask arg0 writes `lines` = arg2[31:0] lines from line arg1, line L = pattern(seed, L)
2358// with seed = arg2[63:32]; done when each channel reports its last beat accepted.
2359// CHECK (2): every channel in the mask reads those lines back; result0 = lines that differ.
2360// HOST_TO_MEM (3): `lines` host lines from host line arg0 into channel arg1[63:56] at line arg1[AW-1:0]; a line is
2361// the host line, zero-extended.
2362// MEM_TO_HOST (4): the reverse, a channel line truncated to a host line; done when every host write burst is
2363// acknowledged.
2364// HOST_TO_MEM_PACKED (5): `lines` channel lines at the channel's full width into channel arg1[63:56] at line
2365// arg1[AW-1:0], read packed end to end from host line arg0 on, the fewest host lines that hold them; done
2366// when the channel's writes are done.
2367// MEM_TO_HOST_PACKED (6): the reverse, the channel lines packed end to end into host lines, the last zero past
2368// them; done when every host write burst is acknowledged.
2369// A packed copy moves its bytes through one gather register of 64-bit words: a line leaves it once it holds a
2370// line's words, a line comes into it when its words fit.
2371// INJECT (7): on SECDED channels, the next arg2[31:0] lines written to each channel in mask arg0 have bus bits
2372// arg1[9:0] and arg1[25:16] flipped after they are coded, one bit when the two are equal.
2373// ECC_COUNTS (8): on SECDED channels, result0 = {words found uncorrectable, words corrected} of channel arg0[7:0]
2374// since reset, each modulo 2^32, every line read counted, whichever side took it.
2375// ENGINE_CONFIG (9): with the engine, its token-rows arg0[15:0] and knobs arg0[19:16] {per_block, two_words,
2376// cache_int8, state_f16}, the card port's space arg1[31:0], base arg2[AW-1:0] and log2 granule
2377// arg2[60:56], held until the next.
2378// ENGINE_TABLE (10): with the engine, `lines` host lines from host line arg0 into its table arg1[34:32] from entry
2379// arg1[31:0] on, a line an entry; done when the last is written.
2380// ENGINE_RUN (11): with the engine, the step from job word arg0[31:0] to arg1[31:0]: the card port's fault cleared,
2381// the engine started, the channels the card port's until the engine is idle again; result0 = {the word
2382// the sequencer reached, 30'b0, the card port's fault, the engine's fault}.
2383// STORE_READ (12): with the engine, `lines` beats of its store from beat arg1[31:0] into host lines from host line
2384// arg0, each beat read a cycle after its address is set; done when every host write burst is acknowledged.
2385// Any other op completes at once with code 0x80, as INJECT and ECC_COUNTS do on channels without SECDED and the
2386// engine's four without the engine; NOP (0) with code 0.
2387module logos_bringup_core #(
2388 parameter NB = 4, // memory channels
2389 parameter AW = 27, // channel line-address bits
2390 parameter DWM = 512, // channel line bits
2391 parameter ECC = 0, // the channels' lines SECDED-coded
2392 parameter ENGINE = 0, // an engine and its card port behind the core
2393 parameter HAW = 42, // host line-address bits
2394 parameter DWH = 512, // host line bits
2395 parameter LW = 32,
2396 parameter HMAXB = 64, // lines a host write burst
2397 parameter HB = 16 // log2 of the cycles between a running command's progress records
2398) (
2399 input clk,
2400 input ce,
2401 input rst_n,
2402 input cmd_valid,
2403 output cmd_ready,
2404 input [255:0] cmd_data,
2405 output st_valid,
2406 input st_ready,
2407 output [255:0] st_data,
2408 // Channel descriptors {write, address, lines}, lines read, lines to write, write descriptors finished.
2409 output [NB-1:0] md_valid,
2410 input [NB-1:0] md_ready,
2411 output [NB*(AW+LW+1)-1:0] md_data,
2412 input [NB-1:0] mr_valid,
2413 output [NB-1:0] mr_ready,
2414 input [NB*DWM-1:0] mr_data,
2415 input [NB*16-1:0] mr_ecc, // each line read's words' verdicts {uncorrectable, corrected}
2416 output [NB-1:0] mw_valid,
2417 input [NB-1:0] mw_ready,
2418 output [NB*DWM-1:0] mw_data,
2419 output [NB*21-1:0] mw_flip, // the bus bits each line written flips {enable, b, a}
2420 input [NB-1:0] mdone_valid,
2421 output [NB-1:0] mdone_ready,
2422 // Host read and write descriptors {address, lines}, host lines read, host lines to write, host write acknowledgements.
2423 output hrd_valid,
2424 input hrd_ready,
2425 output [HAW+LW-1:0] hrd_data,
2426 input hr_valid,
2427 output hr_ready,
2428 input [DWH-1:0] hr_data,
2429 output hwd_valid,
2430 input hwd_ready,
2431 output [HAW+LW-1:0] hwd_data,
2432 output hw_valid,
2433 input hw_ready,
2434 output [DWH-1:0] hw_data,
2435 input hack_valid,
2436 output hack_ready,
2437 // The lines each read FIFO gives up, to whichever side takes them; a line crossing any channel, either way.
2438 input [NB-1:0] mr_taken,
2439 input moved,
2440 // The engine: its run, its configuration, its tables and store; the card port's map, its fault, and the channels.
2441 output reg card_mode,
2442 output reg eng_start,
2443 output reg [31:0] eng_job_at,
2444 output reg [31:0] eng_job_end,
2445 output reg [15:0] eng_rows,
2446 output reg [3:0] eng_knobs,
2447 input eng_busy,
2448 input eng_fault,
2449 input [31:0] eng_pc,
2450 output eng_host_wr,
2451 output reg [2:0] eng_host_table,
2452 output [31:0] eng_host_addr,
2453 output [DWH-1:0] eng_host_wdata,
2454 output [31:0] eng_host_raddr,
2455 input [DWH-1:0] eng_host_rdata,
2456 output reg [31:0] port_space,
2457 output reg [AW-1:0] port_base,
2458 output reg [4:0] port_granule_log2,
2459 output reg port_clear,
2460 input port_fault
2461);
2462 /*OPS*/
2463 localparam LANES = DWM / 64;
2464 localparam BB = NB > 1 ? $clog2(NB) : 1;
2465 localparam [LW-1:0] HMAXL = HMAXB;
2466 localparam HMAXB_LOG2 = $clog2(HMAXB);
2467
2468 // Line L of a fill seeded `seed`: each 64-bit lane distinct, so a swapped lane or line reads back wrong.
2469 function [DWM-1:0] pattern(input [31:0] seed, input [31:0] line);
2470 integer k;
2471 reg [31:0] lane;
2472 begin
2473 for (k = 0; k < LANES; k = k + 1) begin
2474 lane = k;
2475 pattern[64*k +: 64] = {seed ^ line ^ (lane * 32'h9e3779b9), ~line + lane * 32'h85ebca6b};
2476 end
2477 end
2478 endfunction
2479
2480 // The 32 low bits of channel line `base + i`, the line a pattern is seeded by.
2481 function [31:0] line_of(input [AW-1:0] base_line, input [LW-1:0] i);
2482 reg [AW-1:0] at;
2483 begin
2484 at = base_line + i[AW-1:0];
2485 line_of = at;
2486 end
2487 endfunction
2488
2489 reg busy, reported;
2490 reg [7:0] op;
2491 reg [55:0] tag;
2492 reg [7:0] code;
2493 reg [63:0] seq, cycles, started, result0;
2494 reg [NB-1:0] mask, need_desc, wrote, done;
2495 reg [AW-1:0] base;
2496 reg [LW-1:0] lines;
2497 reg [31:0] seed;
2498 reg [HAW-1:0] hbase;
2499 reg [7:0] bank;
2500 reg [LW-1:0] sent [0:NB-1];
2501 reg [LW-1:0] seen [0:NB-1];
2502 reg [63:0] wrong [0:NB-1];
2503 reg need_hdesc;
2504 reg [LW-1:0] acks, bursts;
2505 // SECDED: lines each channel still flips and the two bus bits, the words corrected and lost since reset.
2506 reg [31:0] flips_left [0:NB-1];
2507 reg [19:0] flip_bits [0:NB-1];
2508 reg [31:0] fixed [0:NB-1];
2509 reg [31:0] lost [0:NB-1];
2510
2511 function [3:0] ones(input [7:0] v);
2512 integer k;
2513 begin
2514 ones = 0;
2515 for (k = 0; k < 8; k = k + 1) ones = ones + v[k];
2516 end
2517 endfunction
2518
2519 wire packed_in = op == HOST_TO_MEM_PACKED;
2520 wire packed_out = op == MEM_TO_HOST_PACKED;
2521 wire eng_table = op == ENGINE_TABLE;
2522 wire store_read = op == STORE_READ;
2523 // The engine's tables and store: the entry a table write starts at and the host lines it has taken; the beat a store
2524 // read starts at, the beats it has sent, and whether the current beat's address has stood a cycle.
2525 reg [31:0] tbase, taken, sbase, sent_beats;
2526 reg beat_ready;
2527 reg [1:0] run_hold;
2528 // A table write's end, from registers: every line taken, or every line but the one taken now.
2529 wire taken_all = taken == lines, taken_last = taken + 32'd1 == lines;
2530 wire to_mem = op == FILL || op == HOST_TO_MEM || packed_in;
2531 wire from_host = op == HOST_TO_MEM || packed_in;
2532 wire to_host = op == MEM_TO_HOST || packed_out;
2533
2534 // The packed copies' gather register: `gn` 64-bit words of the bytes in flight, the rest zero.
2535 localparam LWD = DWM / 64, HWD = DWH / 64, GW = LWD + HWD;
2536 localparam [7:0] LWD8 = LWD, HWD8 = HWD, GW8 = GW;
2537 reg [64*GW-1:0] g;
2538 reg [7:0] gn;
2539 reg [LW-1:0] hlines;
2540 // The host lines a command moves: a packed copy's channel lines end to end in host lines, any other one each.
2541 wire [LW-1:0] cmd_lines = cmd_data[192+LW-1:192];
2542 wire [LW+7:0] cmd_words = cmd_lines * LWD8;
2543 wire [LW+7:0] cmd_hwords = (cmd_words + HWD8 - 8'd1) / HWD8;
2544 wire [LW-1:0] cmd_hlines = (cmd_data[7:0] == HOST_TO_MEM_PACKED || cmd_data[7:0] == MEM_TO_HOST_PACKED)
2545 ? cmd_hwords[LW-1:0] : cmd_lines;
2546
2547 // Progress: cycles in which a line or the sequencer moved, each counted the step after (`moved_q`), so the count's
2548 // carry chain never waits on the channels' handshakes; `told` the count the last progress record carried.
2549 reg [63:0] progress, told;
2550 reg moved_q;
2551 reg [31:0] last_pc;
2552 reg [HB-1:0] beat_timer;
2553 reg beat;
2554 wire host_moved = (hr_valid && hr_ready) || (hw_valid && hw_ready);
2555 assign cmd_ready = !busy;
2556 assign st_valid = busy && (reported || beat);
2557 assign st_data = reported ? {cycles - started, result0, {tag, code}, seq + 1'b1}
2558 : {cycles - started, eng_pc, progress[31:0], {tag, RUNNING}, seq};
2559
2560 wire [BB-1:0] ch_sel = bank[BB-1:0];
2561 wire [DWM-1:0] host_src = mr_data[ch_sel*DWM +: DWM];
2562 // Into a channel: a line leaves the register once it holds one, a host line comes in when its words fit beside what
2563 // stays. The host lines asked for are the fewest that hold the channel lines, so the last channel line takes the last
2564 // of them.
2565 wire pin_emit = busy && packed_in && mw_valid[ch_sel] && mw_ready[ch_sel];
2566 wire [7:0] pin_gn1 = pin_emit ? gn - LWD8 : gn;
2567 wire pin_take = busy && packed_in && hr_valid && pin_gn1 + HWD8 <= GW8;
2568 // Out of a channel: a host line leaves once the register holds one, or holds the last channel line's last words;
2569 // a channel line comes in when its words fit beside what stays.
2570 wire pout_valid = busy && packed_out && (gn >= HWD8 || (seen[ch_sel] == lines && gn != 0));
2571 wire pout_emit = pout_valid && hw_ready;
2572 wire [7:0] pout_gn1 = pout_emit ? (gn > HWD8 ? gn - HWD8 : 8'd0) : gn;
2573 wire pout_take = busy && packed_out && mr_valid[ch_sel] && seen[ch_sel] < lines && pout_gn1 + LWD8 <= GW8;
2574 genvar b;
2575 generate for (b = 0; b < NB; b = b + 1) begin : ch
2576 wire use_this = busy && mask[b];
2577 wire mem_side = (op == HOST_TO_MEM || op == MEM_TO_HOST || packed_in || packed_out) ? bank == b : 1'b1;
2578 assign md_valid[b] = use_this && need_desc[b];
2579 assign md_data[b*(AW+LW+1) +: AW+LW+1] = {to_mem, base, lines};
2580 assign mw_valid[b] = use_this && to_mem && sent[b] < lines &&
2581 (packed_in ? (mem_side && gn >= LWD8) : from_host ? (mem_side && hr_valid) : 1'b1);
2582 assign mw_data[b*DWM +: DWM] = packed_in ? g[DWM-1:0] : from_host ? {{(DWM-DWH){1'b0}}, hr_data}
2583 : pattern(seed, line_of(base, sent[b]));
2584 assign mw_flip[b*21 +: 21] = {ECC != 0 && flips_left[b] != 0, flip_bits[b]};
2585 assign mr_ready[b] = use_this && !to_mem && (packed_out ? (mem_side && pout_take) : to_host ? (mem_side && hw_ready) : 1'b1);
2586 assign mdone_ready[b] = 1'b1;
2587 end endgenerate
2588
2589 assign hr_ready = packed_in ? pin_take : eng_table ? busy && taken < lines
2590 : busy && from_host && mw_ready[ch_sel] && sent[ch_sel] < lines;
2591 assign hw_valid = packed_out ? pout_valid : store_read ? busy && sent_beats < lines && beat_ready
2592 : busy && to_host && mr_valid[ch_sel];
2593 assign hw_data = packed_out ? g[DWH-1:0] : store_read ? eng_host_rdata : host_src[DWH-1:0];
2594 assign hrd_valid = busy && (from_host || eng_table) && need_hdesc;
2595 assign hrd_data = {hbase, hlines};
2596 assign hwd_valid = busy && (to_host || store_read) && need_hdesc;
2597 assign hwd_data = {hbase, hlines};
2598 assign hack_ready = 1'b1;
2599 assign eng_host_wr = busy && eng_table && hr_valid && hr_ready;
2600 assign eng_host_addr = tbase + taken;
2601 assign eng_host_wdata = hr_data;
2602 assign eng_host_raddr = sbase + sent_beats;
2603
2604 integer i;
2605 reg [63:0] total;
2606 always @(*) begin
2607 total = 0;
2608 for (i = 0; i < NB; i = i + 1) total = total + wrong[i];
2609 end
2610
2611 always @(posedge clk or negedge rst_n) begin
2612 if (!rst_n) begin
2613 busy <= 1'b0; reported <= 1'b0; op <= 0; tag <= 0; code <= 0; seq <= 0; cycles <= 0; started <= 0;
2614 result0 <= 0; mask <= 0; need_desc <= 0; wrote <= 0; done <= 0; base <= 0; lines <= 0; seed <= 0;
2615 hbase <= 0; bank <= 0; need_hdesc <= 1'b0; acks <= 0; bursts <= 0; g <= 0; gn <= 0; hlines <= 0;
2616 for (i = 0; i < NB; i = i + 1) begin
2617 sent[i] <= 0; seen[i] <= 0; wrong[i] <= 0; flips_left[i] <= 0; flip_bits[i] <= 0; fixed[i] <= 0; lost[i] <= 0;
2618 end
2619 card_mode <= 1'b0; eng_start <= 1'b0; eng_job_at <= 0; eng_job_end <= 0; eng_rows <= 0; eng_knobs <= 0;
2620 eng_host_table <= 0; port_space <= 0; port_base <= 0; port_granule_log2 <= 0; port_clear <= 1'b0;
2621 tbase <= 0; taken <= 0; sbase <= 0; sent_beats <= 0; beat_ready <= 1'b0; run_hold <= 0;
2622 progress <= 0; told <= 0; last_pc <= 0; beat_timer <= 0; beat <= 1'b0; moved_q <= 1'b0;
2623 end else if (ce) begin
2624 cycles <= cycles + 1'b1;
2625 moved_q <= moved || host_moved || eng_pc != last_pc;
2626 progress <= progress + {63'd0, moved_q};
2627 last_pc <= eng_pc;
2628 eng_start <= 1'b0;
2629 port_clear <= 1'b0;
2630 if (!busy) begin
2631 if (cmd_valid) begin
2632 busy <= 1'b1;
2633 reported <= 1'b0;
2634 beat <= 1'b0;
2635 beat_timer <= 0;
2636 told <= progress;
2637 op <= cmd_data[7:0];
2638 tag <= cmd_data[63:8];
2639 started <= cycles;
2640 code <= 8'd0;
2641 result0 <= 0;
2642 seed <= cmd_data[255:224];
2643 lines <= cmd_data[192+LW-1:192];
2644 need_hdesc <= cmd_data[7:0] == HOST_TO_MEM || cmd_data[7:0] == MEM_TO_HOST ||
2645 cmd_data[7:0] == HOST_TO_MEM_PACKED || cmd_data[7:0] == MEM_TO_HOST_PACKED ||
2646 (ENGINE != 0 && (cmd_data[7:0] == ENGINE_TABLE || cmd_data[7:0] == STORE_READ));
2647 hbase <= cmd_data[64+HAW-1:64];
2648 taken <= 0;
2649 sent_beats <= 0;
2650 beat_ready <= 1'b0;
2651 acks <= 0;
2652 hlines <= cmd_hlines;
2653 bursts <= (cmd_hlines + HMAXL - 1'b1) >> HMAXB_LOG2;
2654 g <= 0;
2655 gn <= 0;
2656 for (i = 0; i < NB; i = i + 1) begin sent[i] <= 0; seen[i] <= 0; wrong[i] <= 0; end
2657 done <= 0;
2658 case (cmd_data[7:0])
2659 FILL, CHECK: begin
2660 mask <= cmd_data[64+NB-1:64];
2661 need_desc <= cmd_data[64+NB-1:64];
2662 base <= cmd_data[128+AW-1:128];
2663 end
2664 HOST_TO_MEM, MEM_TO_HOST, HOST_TO_MEM_PACKED, MEM_TO_HOST_PACKED: begin
2665 hbase <= cmd_data[64+HAW-1:64];
2666 bank <= cmd_data[191:184];
2667 mask <= {{(NB-1){1'b0}}, 1'b1} << cmd_data[184+BB-1:184];
2668 need_desc <= {{(NB-1){1'b0}}, 1'b1} << cmd_data[184+BB-1:184];
2669 base <= cmd_data[128+AW-1:128];
2670 end
2671 NOP: begin mask <= 0; need_desc <= 0; reported <= 1'b1; end
2672 INJECT: begin
2673 mask <= 0; need_desc <= 0; reported <= 1'b1;
2674 if (ECC == 0) code <= UNKNOWN_OP;
2675 else for (i = 0; i < NB; i = i + 1) if (cmd_data[64 + i]) begin
2676 flips_left[i] <= cmd_data[192 +: 32];
2677 flip_bits[i] <= {cmd_data[144 +: 10], cmd_data[128 +: 10]};
2678 end
2679 end
2680 ECC_COUNTS: begin
2681 mask <= 0; need_desc <= 0; reported <= 1'b1;
2682 if (ECC == 0) code <= UNKNOWN_OP;
2683 else result0 <= {lost[cmd_data[64 +: BB]], fixed[cmd_data[64 +: BB]]};
2684 end
2685 ENGINE_CONFIG, ENGINE_TABLE, ENGINE_RUN, STORE_READ: begin
2686 mask <= 0; need_desc <= 0;
2687 if (ENGINE == 0) begin
2688 code <= UNKNOWN_OP; reported <= 1'b1;
2689 end else case (cmd_data[7:0])
2690 ENGINE_CONFIG: begin
2691 eng_rows <= cmd_data[64 +: 16];
2692 eng_knobs <= cmd_data[80 +: 4];
2693 port_space <= cmd_data[128 +: 32];
2694 port_base <= cmd_data[192 +: AW];
2695 port_granule_log2 <= cmd_data[248 +: 5];
2696 reported <= 1'b1;
2697 end
2698 ENGINE_TABLE: begin
2699 tbase <= cmd_data[128 +: 32];
2700 eng_host_table <= cmd_data[160 +: 3];
2701 end
2702 ENGINE_RUN: begin
2703 eng_job_at <= cmd_data[64 +: 32];
2704 eng_job_end <= cmd_data[128 +: 32];
2705 card_mode <= 1'b1;
2706 port_clear <= 1'b1;
2707 eng_start <= 1'b1;
2708 run_hold <= 2'd2;
2709 end
2710 default: sbase <= cmd_data[128 +: 32];
2711 endcase
2712 end
2713 default: begin mask <= 0; need_desc <= 0; code <= UNKNOWN_OP; reported <= 1'b1; end
2714 endcase
2715 end
2716 end else if (reported) begin
2717 beat <= 1'b0;
2718 if (st_ready) begin
2719 busy <= 1'b0;
2720 reported <= 1'b0;
2721 seq <= seq + 1'b1;
2722 end
2723 end else begin
2724 beat_timer <= beat_timer + 1'b1;
2725 if (beat) begin
2726 if (st_ready) begin beat <= 1'b0; told <= progress; end
2727 end else if (&beat_timer && progress != told) beat <= 1'b1;
2728 for (i = 0; i < NB; i = i + 1) begin
2729 if (md_valid[i] && md_ready[i]) need_desc[i] <= 1'b0;
2730 if (mw_valid[i] && mw_ready[i]) begin
2731 sent[i] <= sent[i] + 1'b1;
2732 if (flips_left[i] != 0) flips_left[i] <= flips_left[i] - 1'b1;
2733 end
2734 if (mr_valid[i] && mr_ready[i]) begin
2735 seen[i] <= seen[i] + 1'b1;
2736 if (op == CHECK && mr_data[i*DWM +: DWM] !== pattern(seed, line_of(base, seen[i]))) wrong[i] <= wrong[i] + 1'b1;
2737 end
2738 if (mr_valid[i] && mr_taken[i]) begin
2739 fixed[i] <= fixed[i] + ones(mr_ecc[i*16 +: 8]);
2740 lost[i] <= lost[i] + ones(mr_ecc[i*16 + 8 +: 8]);
2741 end
2742 if (mdone_valid[i] && mask[i]) done[i] <= 1'b1;
2743 end
2744 if ((hrd_valid && hrd_ready) || (hwd_valid && hwd_ready)) need_hdesc <= 1'b0;
2745 if (hack_valid) acks <= acks + 1'b1;
2746 if (eng_host_wr) taken <= taken + 1'b1;
2747 if (store_read) begin
2748 if (hw_valid && hw_ready) begin
2749 sent_beats <= sent_beats + 1'b1;
2750 beat_ready <= 1'b0;
2751 end else beat_ready <= sent_beats < lines;
2752 end
2753 if (run_hold != 0) run_hold <= run_hold - 1'b1;
2754 if (packed_in) begin
2755 g <= (pin_emit ? g >> DWM : g) | (pin_take ? {{(64*GW-DWH){1'b0}}, hr_data} << (64 * pin_gn1) : {(64*GW){1'b0}});
2756 gn <= pin_gn1 + (pin_take ? HWD8 : 8'd0);
2757 end
2758 if (packed_out) begin
2759 g <= (pout_emit ? g >> DWH : g) | (pout_take ? {{(64*GW-DWM){1'b0}}, host_src} << (64 * pout_gn1) : {(64*GW){1'b0}});
2760 gn <= pout_gn1 + (pout_take ? LWD8 : 8'd0);
2761 end
2762 case (op)
2763 FILL, HOST_TO_MEM, HOST_TO_MEM_PACKED: if ((done | (mdone_valid & mask)) == mask && need_desc == 0) begin
2764 reported <= 1'b1;
2765 end
2766 // Every line compared, in the registers result0 sums, before the record is cut.
2767 CHECK: if (all_seen(mask) && need_desc == 0) begin
2768 reported <= 1'b1;
2769 result0 <= total;
2770 end
2771 MEM_TO_HOST, MEM_TO_HOST_PACKED, STORE_READ:
2772 if (acks + {{(LW-1){1'b0}}, hack_valid} == bursts && !need_hdesc) reported <= 1'b1;
2773 ENGINE_TABLE: if ((eng_host_wr ? taken_last : taken_all) && !need_hdesc) reported <= 1'b1;
2774 ENGINE_RUN: if (run_hold == 0 && !eng_busy) begin
2775 reported <= 1'b1;
2776 card_mode <= 1'b0;
2777 result0 <= {eng_pc, 30'd0, port_fault, eng_fault};
2778 end
2779 default: reported <= 1'b1;
2780 endcase
2781 end
2782 end
2783 end
2784
2785 function all_seen(input [NB-1:0] m);
2786 integer j;
2787 begin
2788 all_seen = 1'b1;
2789 for (j = 0; j < NB; j = j + 1) if (m[j] && seen[j] < lines) all_seen = 1'b0;
2790 end
2791 endfunction
2792endmodule
2793"##;
2794
2795const SHELL: &str = r##"// One memory channel as the core sees it: descriptors and lines to write (with ECC the bus bits each flips) crossing
2796// into the channel's clock through FIFOs, lines read (with ECC their words' verdicts) and write-done tokens crossing
2797// out of it, the channel's stream engine on the bank's Avalon-MM port. A read-data FIFO is deep enough to hold
2798// a whole run of bursts the channel has in flight. The FIFOs' sides toward the core step at its step (uce).
2799module logos_d5005_channel #(
2800 parameter AW = 27, // channel line-address bits
2801 parameter DWM = 512, // channel bus bits a line
2802 parameter ECC = 0, // 1: each line's eight words SECDED(72,64)-coded in the bus's ECC lanes
2803 parameter MBW = 7, // channel burstcount bits
2804 parameter LW = 32,
2805 parameter MAXB = 64, // lines a burst
2806 parameter RAW = 9, // log2 of the read-data FIFO's lines
2807 parameter WAW = 6, // log2 of the write-data FIFO's lines
2808 parameter DWL = ECC ? DWM / 9 * 8 : DWM // line bits the core moves
2809) (
2810 input uclk,
2811 input uce,
2812 input uclk_rst_n,
2813 input mem_clk,
2814 input mem_rst_n,
2815 input md_valid,
2816 output md_ready,
2817 input [AW+LW:0] md_data,
2818 output mr_valid,
2819 input mr_ready,
2820 output [DWL-1:0] mr_data,
2821 output [15:0] mr_ecc,
2822 input mw_valid,
2823 output mw_ready,
2824 input [DWL-1:0] mw_data,
2825 input [20:0] mw_flip,
2826 output mdone_valid,
2827 input mdone_ready,
2828 output [AW-1:0] mem_address,
2829 output mem_read,
2830 output mem_write,
2831 output [MBW-1:0] mem_burstcount,
2832 output [DWM-1:0] mem_writedata,
2833 output [DWM/8-1:0] mem_byteenable,
2834 input mem_waitrequest,
2835 input [DWM-1:0] mem_readdata,
2836 input mem_readdatavalid
2837);
2838 localparam DLW = AW + LW + 1;
2839 localparam RFW = ECC ? DWL + 16 : DWL; // a read-FIFO word: the line, with ECC its words' verdicts
2840 localparam WFW = ECC ? DWL + 21 : DWL; // a write-FIFO word: the line, with ECC the bus bits it flips
2841 wire d_valid, d_ready, r_push, w_valid, w_ready, done;
2842 wire [DLW-1:0] d_data;
2843 wire [DWL-1:0] r_data, w_data;
2844 wire [15:0] r_ecc;
2845 wire [20:0] w_flip;
2846 wire [RFW-1:0] r_word, mr_word;
2847 wire [WFW-1:0] w_word, mw_word;
2848 wire [RAW:0] r_room, r_used;
2849 wire [2:0] d_wfree, d_rused, done_wfree, done_rused;
2850 wire [WAW:0] w_wfree, w_rused;
2851 wire r_wready, done_wready, done_token;
2852 generate if (ECC) begin : coded
2853 assign mw_word = {mw_flip, mw_data};
2854 assign {w_flip, w_data} = w_word;
2855 assign r_word = {r_ecc, r_data};
2856 assign {mr_ecc, mr_data} = mr_word;
2857 end else begin : plain
2858 assign mw_word = mw_data;
2859 assign w_data = w_word;
2860 assign w_flip = 21'h0;
2861 assign r_word = r_data;
2862 assign mr_data = mr_word;
2863 assign mr_ecc = 16'h0;
2864 end endgenerate
2865 logos_dcfifo_mlab #(.W(DLW), .AW(2), .HOLDS(1)) desc_fifo(.wclk(uclk), .wce(uce), .wrst_n(uclk_rst_n), .wvalid(md_valid),
2866 .wready(md_ready), .wdata(md_data), .wfree(d_wfree), .rclk(mem_clk), .rce(1'b1), .rrst_n(mem_rst_n), .rvalid(d_valid),
2867 .rready(d_ready), .rdata(d_data), .rused(d_rused));
2868 logos_dcfifo_m20k #(.W(WFW), .AW(WAW), .HOLDS(1)) write_fifo(.wclk(uclk), .wce(uce), .wrst_n(uclk_rst_n), .wvalid(mw_valid),
2869 .wready(mw_ready), .wdata(mw_word), .wfree(w_wfree), .rclk(mem_clk), .rce(1'b1), .rrst_n(mem_rst_n), .rvalid(w_valid),
2870 .rready(w_ready), .rdata(w_word), .rused(w_rused));
2871 logos_dcfifo_m20k #(.W(RFW), .AW(RAW), .HOLDS(1)) read_fifo(.wclk(mem_clk), .wce(1'b1), .wrst_n(mem_rst_n), .wvalid(r_push),
2872 .wready(r_wready), .wdata(r_word), .wfree(r_room), .rclk(uclk), .rce(uce), .rrst_n(uclk_rst_n), .rvalid(mr_valid),
2873 .rready(mr_ready), .rdata(mr_word), .rused(r_used));
2874 logos_dcfifo_mlab #(.W(1), .AW(2), .TOKENS(1)) done_fifo(.wclk(mem_clk), .wce(1'b1), .wrst_n(mem_rst_n), .wvalid(done),
2875 .wready(done_wready), .wdata(1'b1), .wfree(done_wfree), .rclk(uclk), .rce(uce), .rrst_n(uclk_rst_n),
2876 .rvalid(mdone_valid), .rready(mdone_ready), .rdata(done_token), .rused(done_rused));
2877 wire [RAW:0] owed;
2878 wire busy;
2879 logos_stream_avmm #(.AW(AW), .DW(DWM), .BW(MBW), .LW(LW), .MAXB(MAXB), .RW(RAW+1), .ECC(ECC)) stream(
2880 .clk(mem_clk), .rst_n(mem_rst_n), .d_valid(d_valid), .d_ready(d_ready), .d_data(d_data),
2881 .r_valid(r_push), .r_data(r_data), .r_ecc(r_ecc), .r_room(r_room), .w_valid(w_valid), .w_ready(w_ready),
2882 .w_data(w_data), .w_flip(w_flip), .avm_address(mem_address), .avm_read(mem_read), .avm_write(mem_write),
2883 .avm_burstcount(mem_burstcount), .avm_writedata(mem_writedata), .avm_byteenable(mem_byteenable),
2884 .avm_waitrequest(mem_waitrequest), .avm_readdata(mem_readdata), .avm_readdatavalid(mem_readdatavalid),
2885 .owed(owed), .busy(busy), .wdone(done));
2886endmodule
2887
2888// The engine's shell on the D5005: every port the OFS platform gives the accelerator, flat, each in its own clock,
2889// and the only paths between those clocks the dual-clock FIFOs.
2890//
2891// pclk the host channel: MMIO into the registers, host memory through a reader and a writer;
2892// mem_clk one clock a memory channel: its stream engine on the channel's Avalon-MM port;
2893// uclk the user clock: the core, the engine and every FIFO's side toward them.
2894//
2895// The shell makes no clock: its compute side — the core, an engine build's card port and every FIFO's side toward
2896// them — steps in the user clock at cce, high in the user cycles that end with the rising edges of the user clock
2897// divided by the build's divide (every cycle in an undivided build), from crst_n, a reset released at the second.
2898// The core sends descriptors and the lines it writes through FIFOs into each channel's clock and takes the lines
2899// read back through FIFOs out of it (logos_d5005_channel); commands and status records cross between the registers and
2900// the core the same way. With ECC set the channels' lines are SECDED-coded at each bank's port.
2901module logos_d5005_shell #(
2902 parameter NB = 4, // memory channels
2903 parameter AW = 27, // channel line-address bits
2904 parameter DWM = 512, // channel bus bits a line
2905 parameter ECC = 0, // 1: each line's eight words SECDED(72,64)-coded in the bus's ECC lanes
2906 parameter MBW = 7, // channel burstcount bits
2907 parameter HAW = 51, // host line-address bits
2908 parameter DWH = 512, // host line bits
2909 parameter HBW = 7, // host burstcount bits
2910 parameter MAW = 15, // MMIO word-address bits
2911 parameter MAXB = 64, // lines a burst
2912 parameter LW = 32,
2913 parameter RAW = 9, // log2 of a read-data FIFO's lines
2914 parameter WAW = 6, // log2 of a write-data FIFO's lines
2915 parameter [63:0] AFU_ID_L = 64'h0,
2916 parameter [63:0] AFU_ID_H = 64'h0,
2917 parameter [63:0] BUILD = 64'h0,
2918 parameter HB = 16 // log2 of the core cycles between a running command's progress records, well inside a
2919 // host's wait: 42 ms of the engine build's 1.5625 MHz core against the card's minute of
2920 // reads, some 2.6M reads against 2^24 in a simulation of the divided clock
2921) (
2922 input pclk,
2923 input pclk_rst_n,
2924 input uclk,
2925 input uclk_rst_n,
2926 // MMIO, a 64-bit Avalon-MM slave.
2927 input [MAW-1:0] mmio_address,
2928 input mmio_read,
2929 input mmio_write,
2930 input [63:0] mmio_writedata,
2931 input [7:0] mmio_byteenable,
2932 output mmio_waitrequest,
2933 output [63:0] mmio_readdata,
2934 output mmio_readdatavalid,
2935 // Host memory, an Avalon-MM read/write master.
2936 output [HAW-1:0] host_rd_address,
2937 output host_rd_read,
2938 output [HBW-1:0] host_rd_burstcount,
2939 input host_rd_waitrequest,
2940 input [DWH-1:0] host_rd_readdata,
2941 input host_rd_readdatavalid,
2942 output [HAW-1:0] host_wr_address,
2943 output host_wr_write,
2944 output [HBW-1:0] host_wr_burstcount,
2945 output [DWH-1:0] host_wr_writedata,
2946 output [DWH/8-1:0] host_wr_byteenable,
2947 input host_wr_waitrequest,
2948 input host_wr_writeresponsevalid,
2949 // The memory channels, each an Avalon-MM master in its own clock.
2950 input [NB-1:0] mem_clk,
2951 input [NB-1:0] mem_rst_n,
2952 output [NB*AW-1:0] mem_address,
2953 output [NB-1:0] mem_read,
2954 output [NB-1:0] mem_write,
2955 output [NB*MBW-1:0] mem_burstcount,
2956 output [NB*DWM-1:0] mem_writedata,
2957 output [NB*DWM/8-1:0] mem_byteenable,
2958 input [NB-1:0] mem_waitrequest,
2959 input [NB*DWM-1:0] mem_readdata,
2960 input [NB-1:0] mem_readdatavalid
2961);
2962 localparam DLW = AW + LW + 1;
2963 localparam HDW = HAW + LW;
2964 localparam DWL = ECC ? DWM / 9 * 8 : DWM; // line bits the core moves
2965/*ENGINE_PARAM*/
2966/*COMPUTE*/
2967
2968 // Registers <-> core.
2969 wire cmd_wvalid, cmd_wready, cmd_rvalid, cmd_rready;
2970 wire [255:0] cmd_wdata, cmd_rdata;
2971 wire [4:0] cmd_wfree, cmd_rused;
2972 wire st_wvalid, st_wready, st_rvalid, st_rready;
2973 wire [255:0] st_wdata, st_rdata;
2974 logos_csr #(.MAW(MAW), .AFU_ID_L(AFU_ID_L), .AFU_ID_H(AFU_ID_H), .BUILD(BUILD), .CW(5)) csr(
2975 .clk(pclk), .rst_n(pclk_rst_n), .mmio_address(mmio_address), .mmio_read(mmio_read), .mmio_write(mmio_write),
2976 .mmio_writedata(mmio_writedata), .mmio_byteenable(mmio_byteenable), .mmio_waitrequest(mmio_waitrequest),
2977 .mmio_readdata(mmio_readdata), .mmio_readdatavalid(mmio_readdatavalid), .cmd_valid(cmd_wvalid),
2978 .cmd_ready(cmd_wready), .cmd_data(cmd_wdata), .cmd_space(cmd_wfree), .st_valid(st_rvalid), .st_ready(st_rready),
2979 .st_data(st_rdata));
2980 logos_dcfifo_mlab #(.W(256), .AW(4)) cmd_fifo(.wclk(pclk), .wce(1'b1), .wrst_n(pclk_rst_n), .wvalid(cmd_wvalid),
2981 .wready(cmd_wready), .wdata(cmd_wdata), .wfree(cmd_wfree), .rclk(uclk), .rce(cce), .rrst_n(crst_n), .rvalid(cmd_rvalid),
2982 .rready(cmd_rready), .rdata(cmd_rdata), .rused(cmd_rused));
2983 wire [4:0] st_wfree, st_rused;
2984 logos_dcfifo_mlab #(.W(256), .AW(4)) st_fifo(.wclk(uclk), .wce(cce), .wrst_n(crst_n), .wvalid(st_wvalid), .wready(st_wready),
2985 .wdata(st_wdata), .wfree(st_wfree), .rclk(pclk), .rce(1'b1), .rrst_n(pclk_rst_n), .rvalid(st_rvalid),
2986 .rready(st_rready), .rdata(st_rdata), .rused(st_rused));
2987
2988 // The core's side of every channel FIFO.
2989 wire [NB-1:0] md_valid, md_ready, mr_valid, mr_ready, mw_valid, mw_ready, mdone_valid, mdone_ready;
2990 wire [NB*DLW-1:0] md_data;
2991 wire [NB*DWL-1:0] mr_data, mw_data;
2992 wire [NB*16-1:0] mr_ecc;
2993 wire [NB*21-1:0] mw_flip;
2994
2995 genvar b;
2996 generate for (b = 0; b < NB; b = b + 1) begin : ch
2997 logos_d5005_channel #(.AW(AW), .DWM(DWM), .ECC(ECC), .MBW(MBW), .LW(LW), .MAXB(MAXB), .RAW(RAW), .WAW(WAW)) channel(
2998 .uclk(uclk), .uce(cce), .uclk_rst_n(crst_n), .mem_clk(mem_clk[b]), .mem_rst_n(mem_rst_n[b]),
2999 .md_valid(md_valid[b]), .md_ready(md_ready[b]), .md_data(md_data[b*DLW +: DLW]),
3000 .mr_valid(mr_valid[b]), .mr_ready(mr_ready[b]), .mr_data(mr_data[b*DWL +: DWL]), .mr_ecc(mr_ecc[b*16 +: 16]),
3001 .mw_valid(mw_valid[b]), .mw_ready(mw_ready[b]), .mw_data(mw_data[b*DWL +: DWL]), .mw_flip(mw_flip[b*21 +: 21]),
3002 .mdone_valid(mdone_valid[b]), .mdone_ready(mdone_ready[b]),
3003 .mem_address(mem_address[b*AW +: AW]), .mem_read(mem_read[b]), .mem_write(mem_write[b]),
3004 .mem_burstcount(mem_burstcount[b*MBW +: MBW]), .mem_writedata(mem_writedata[b*DWM +: DWM]),
3005 .mem_byteenable(mem_byteenable[b*DWM/8 +: DWM/8]), .mem_waitrequest(mem_waitrequest[b]),
3006 .mem_readdata(mem_readdata[b*DWM +: DWM]), .mem_readdatavalid(mem_readdatavalid[b]));
3007 end endgenerate
3008
3009 // Host memory.
3010 wire hrd_valid, hrd_ready, hwd_valid, hwd_ready, hr_valid, hr_ready, hw_valid, hw_ready, hack_valid, hack_ready;
3011 wire [HDW-1:0] hrd_data, hwd_data;
3012 wire [DWH-1:0] hr_data, hw_data;
3013 wire phrd_valid, phrd_ready, phwd_valid, phwd_ready, phr_push, phr_wready, phw_valid, phw_ready, pack_wready;
3014 wire [HDW-1:0] phrd_data, phwd_data;
3015 wire [DWH-1:0] phr_data, phw_data;
3016 wire [2:0] hrd_wfree, hrd_rused, hwd_wfree, hwd_rused;
3017 wire [RAW:0] hr_room, hr_used;
3018 wire [WAW:0] hw_wfree, hw_rused;
3019 wire [4:0] hack_wfree, hack_rused;
3020 wire hack_token;
3021 logos_dcfifo_mlab #(.W(HDW), .AW(2), .HOLDS(1)) hrd_fifo(.wclk(uclk), .wce(cce), .wrst_n(crst_n), .wvalid(hrd_valid), .wready(hrd_ready),
3022 .wdata(hrd_data), .wfree(hrd_wfree), .rclk(pclk), .rce(1'b1), .rrst_n(pclk_rst_n), .rvalid(phrd_valid),
3023 .rready(phrd_ready), .rdata(phrd_data), .rused(hrd_rused));
3024 logos_dcfifo_mlab #(.W(HDW), .AW(2), .HOLDS(1)) hwd_fifo(.wclk(uclk), .wce(cce), .wrst_n(crst_n), .wvalid(hwd_valid), .wready(hwd_ready),
3025 .wdata(hwd_data), .wfree(hwd_wfree), .rclk(pclk), .rce(1'b1), .rrst_n(pclk_rst_n), .rvalid(phwd_valid),
3026 .rready(phwd_ready), .rdata(phwd_data), .rused(hwd_rused));
3027 logos_dcfifo_m20k #(.W(DWH), .AW(RAW), .HOLDS(1)) hr_fifo(.wclk(pclk), .wce(1'b1), .wrst_n(pclk_rst_n), .wvalid(phr_push),
3028 .wready(phr_wready), .wdata(phr_data), .wfree(hr_room), .rclk(uclk), .rce(cce), .rrst_n(crst_n), .rvalid(hr_valid),
3029 .rready(hr_ready), .rdata(hr_data), .rused(hr_used));
3030 logos_dcfifo_m20k #(.W(DWH), .AW(WAW), .HOLDS(1)) hw_fifo(.wclk(uclk), .wce(cce), .wrst_n(crst_n), .wvalid(hw_valid), .wready(hw_ready),
3031 .wdata(hw_data), .wfree(hw_wfree), .rclk(pclk), .rce(1'b1), .rrst_n(pclk_rst_n), .rvalid(phw_valid),
3032 .rready(phw_ready), .rdata(phw_data), .rused(hw_rused));
3033 // A host write response cannot wait: a token, counted on the FIFO's write side until it is taken.
3034 logos_dcfifo_mlab #(.W(1), .AW(4), .TOKENS(1)) hack_fifo(.wclk(pclk), .wce(1'b1), .wrst_n(pclk_rst_n), .wvalid(host_wr_writeresponsevalid),
3035 .wready(pack_wready), .wdata(1'b1), .wfree(hack_wfree), .rclk(uclk), .rce(cce), .rrst_n(crst_n), .rvalid(hack_valid),
3036 .rready(hack_ready), .rdata(hack_token), .rused(hack_rused));
3037 wire [RAW:0] howed;
3038 wire hrd_active, hwd_active, hw_done;
3039 logos_stream_reader #(.AW(HAW), .DW(DWH), .BW(HBW), .LW(LW), .MAXB(MAXB), .RW(RAW+1)) host_reader(
3040 .clk(pclk), .rst_n(pclk_rst_n), .d_valid(phrd_valid), .d_ready(phrd_ready), .d_data(phrd_data), .r_valid(phr_push),
3041 .r_data(phr_data), .r_room(hr_room), .avm_address(host_rd_address), .avm_read(host_rd_read),
3042 .avm_burstcount(host_rd_burstcount), .avm_waitrequest(host_rd_waitrequest), .avm_readdata(host_rd_readdata),
3043 .avm_readdatavalid(host_rd_readdatavalid), .owed(howed), .active(hrd_active));
3044 logos_stream_writer #(.AW(HAW), .DW(DWH), .BW(HBW), .LW(LW), .MAXB(MAXB)) host_writer(
3045 .clk(pclk), .rst_n(pclk_rst_n), .d_valid(phwd_valid), .d_ready(phwd_ready), .d_data(phwd_data), .w_valid(phw_valid),
3046 .w_ready(phw_ready), .w_data(phw_data), .avm_address(host_wr_address), .avm_write(host_wr_write),
3047 .avm_burstcount(host_wr_burstcount), .avm_writedata(host_wr_writedata), .avm_byteenable(host_wr_byteenable),
3048 .avm_waitrequest(host_wr_waitrequest), .active(hwd_active), .done(hw_done));
3049
3050 // The channels are the core's, or the card port's while the core runs the engine's step.
3051 wire card_mode;
3052 wire [NB-1:0] c_md_valid, c_mr_ready, c_mw_valid, c_mdone_ready, p_md_valid, p_mr_ready, p_mw_valid, p_mdone_ready;
3053 wire [NB*DLW-1:0] c_md_data, p_md_data;
3054 wire [NB*DWL-1:0] c_mw_data, p_mw_data;
3055 wire [NB*21-1:0] c_mw_flip;
3056 assign md_valid = card_mode ? p_md_valid : c_md_valid;
3057 assign md_data = card_mode ? p_md_data : c_md_data;
3058 assign mr_ready = card_mode ? p_mr_ready : c_mr_ready;
3059 assign mw_valid = card_mode ? p_mw_valid : c_mw_valid;
3060 assign mw_data = card_mode ? p_mw_data : c_mw_data;
3061 assign mw_flip = card_mode ? {(NB*21){1'b0}} : c_mw_flip;
3062 assign mdone_ready = card_mode ? p_mdone_ready : c_mdone_ready;
3063
3064 wire eng_start, eng_busy, eng_fault, eng_host_wr, port_clear, port_fault;
3065 wire [31:0] eng_job_at, eng_job_end, eng_pc, eng_host_addr, eng_host_raddr, port_space;
3066 wire [15:0] eng_rows;
3067 wire [3:0] eng_knobs;
3068 wire [2:0] eng_host_table;
3069 wire [DWH-1:0] eng_host_wdata, eng_host_rdata;
3070 wire [AW-1:0] port_base;
3071 wire [4:0] port_granule_log2;
3072 wire moved = |(mr_valid & mr_ready) || |(mw_valid & mw_ready);
3073 logos_bringup_core #(.NB(NB), .AW(AW), .DWM(DWL), .ECC(ECC), .ENGINE(ENGINE), .HAW(HAW), .DWH(DWH), .LW(LW),
3074 .HMAXB(MAXB), .HB(HB)) core(
3075 .clk(uclk), .ce(cce), .rst_n(crst_n), .cmd_valid(cmd_rvalid), .cmd_ready(cmd_rready), .cmd_data(cmd_rdata),
3076 .st_valid(st_wvalid), .st_ready(st_wready), .st_data(st_wdata), .md_valid(c_md_valid), .md_ready(md_ready),
3077 .md_data(c_md_data), .mr_valid(mr_valid), .mr_ready(c_mr_ready), .mr_data(mr_data), .mr_ecc(mr_ecc),
3078 .mw_valid(c_mw_valid), .mw_ready(mw_ready), .mw_data(c_mw_data), .mw_flip(c_mw_flip), .mdone_valid(mdone_valid),
3079 .mdone_ready(c_mdone_ready),
3080 .hrd_valid(hrd_valid), .hrd_ready(hrd_ready), .hrd_data(hrd_data), .hr_valid(hr_valid), .hr_ready(hr_ready),
3081 .hr_data(hr_data), .hwd_valid(hwd_valid), .hwd_ready(hwd_ready), .hwd_data(hwd_data), .hw_valid(hw_valid),
3082 .hw_ready(hw_ready), .hw_data(hw_data), .hack_valid(hack_valid), .hack_ready(hack_ready), .mr_taken(mr_ready),
3083 .moved(moved),
3084 .card_mode(card_mode), .eng_start(eng_start), .eng_job_at(eng_job_at), .eng_job_end(eng_job_end),
3085 .eng_rows(eng_rows), .eng_knobs(eng_knobs), .eng_busy(eng_busy), .eng_fault(eng_fault), .eng_pc(eng_pc),
3086 .eng_host_wr(eng_host_wr), .eng_host_table(eng_host_table), .eng_host_addr(eng_host_addr),
3087 .eng_host_wdata(eng_host_wdata), .eng_host_raddr(eng_host_raddr), .eng_host_rdata(eng_host_rdata),
3088 .port_space(port_space), .port_base(port_base), .port_granule_log2(port_granule_log2), .port_clear(port_clear),
3089 .port_fault(port_fault));
3090/*ENGINE*/
3091endmodule
3092"##;
3093
3094const SHELL_ENGINE: &str = r##"
3096 // The engine, its memory port the card port on the channels.
3097 wire e_rd, e_rd_valid, e_wr, e_wr_ready;
3098 wire [31:0] e_rd_line, e_wr_line;
3099 wire [511:0] e_rd_data, e_wr_data;
3100 wire [63:0] e_wr_mask;
3101 logos_engine engine(.clk(uclk), .rst(!crst_n), .start(eng_start), .job_at(eng_job_at), .job_end(eng_job_end),
3102 .rows(eng_rows), .state_f16(eng_knobs[0]), .cache_int8(eng_knobs[1]), .two_words(eng_knobs[2]),
3103 .per_block(eng_knobs[3]), .busy(eng_busy), .fault(eng_fault), .pc(eng_pc), .host_wr(eng_host_wr),
3104 .host_table(eng_host_table), .host_addr(eng_host_addr), .host_wdata(eng_host_wdata), .host_raddr(eng_host_raddr),
3105 .host_rdata(eng_host_rdata), .mem_rd(e_rd), .mem_rd_line(e_rd_line), .mem_rd_valid(e_rd_valid),
3106 .mem_rd_data(e_rd_data), .mem_wr(e_wr), .mem_wr_line(e_wr_line), .mem_wr_data(e_wr_data), .mem_wr_mask(e_wr_mask),
3107 .mem_wr_ready(e_wr_ready));
3108 logos_card_port #(.NB(NB), .AW(AW), .LW(LW), .DL(DWL)) port(.clk(uclk), .ce(cce), .rst_n(crst_n), .space(port_space),
3109 .base(port_base), .granule_log2(port_granule_log2), .clear(port_clear), .fault(port_fault), .mem_rd(e_rd),
3110 .mem_rd_line(e_rd_line), .mem_rd_valid(e_rd_valid), .mem_rd_data(e_rd_data), .mem_wr(e_wr),
3111 .mem_wr_line(e_wr_line), .mem_wr_data(e_wr_data), .mem_wr_mask(e_wr_mask), .mem_wr_ready(e_wr_ready),
3112 .md_valid(p_md_valid), .md_ready(md_ready), .md_data(p_md_data), .mr_valid(mr_valid), .mr_ready(p_mr_ready),
3113 .mr_data(mr_data), .mr_ecc(mr_ecc), .mw_valid(p_mw_valid), .mw_ready(mw_ready), .mw_data(p_mw_data),
3114 .mdone_valid(mdone_valid), .mdone_ready(p_mdone_ready));
3115"##;
3116
3117const SHELL_NO_ENGINE: &str = r##"
3119 assign eng_busy = 1'b0;
3120 assign eng_fault = 1'b0;
3121 assign eng_pc = 32'd0;
3122 assign eng_host_rdata = {DWH{1'b0}};
3123 assign port_fault = 1'b0;
3124 assign p_md_valid = {NB{1'b0}};
3125 assign p_md_data = {(NB*DLW){1'b0}};
3126 assign p_mr_ready = {NB{1'b0}};
3127 assign p_mw_valid = {NB{1'b0}};
3128 assign p_mw_data = {(NB*DWL){1'b0}};
3129 assign p_mdone_ready = {NB{1'b0}};
3130"##;
3131
3132const SDC: &str = r##"# The shell's dual-clock FIFOs, constrained as the D5005 FIM constrains its own (syn/setup/dcfifo.sdc): each Gray
3133# pointer's bits cross with their skew under 0.8 of the source clock's period and their net delay under 0.8 of the
3134# destination's, setup and hold relaxed on those paths; the net between a synchronizer's two registers held to 0.8 of
3135# its clock; and a RAM word read on the other side, written two destination edges before its pointer arrived, held to
3136# the pointer's net delay.
3137
3138proc logos_crossing {from to} {
3139 if {[get_collection_size $from] == 0 || [get_collection_size $to] == 0} { return }
3140 set_max_skew -from $from -to $to -get_skew_value_from_clock_period src_clock_period -skew_value_multiplier 0.8
3141 if { ![string equal "quartus_syn" $::TimeQuestInfo(nameofexecutable)] } {
3142 set_net_delay -from $from -to $to -max -get_value_from_clock_period dst_clock_period -value_multiplier 0.8
3143 }
3144 set_max_delay -from $from -to $to 100
3145 set_min_delay -from $from -to $to -100
3146}
3147
3148proc logos_synchronizer {from to} {
3149 if {[get_collection_size $from] == 0 || [get_collection_size $to] == 0} { return }
3150 if { ![string equal "quartus_syn" $::TimeQuestInfo(nameofexecutable)] } {
3151 set_net_delay -from $from -to $to -max -get_value_from_clock_period dst_clock_period -value_multiplier 0.8
3152 }
3153}
3154
3155foreach entity {logos_dcfifo_mlab logos_dcfifo_m20k} {
3156 foreach inst [get_entity_instances $entity] {
3157 logos_crossing [get_keepers $inst|wgray[*]] [get_keepers $inst|wgray_s1[*]]
3158 logos_crossing [get_keepers $inst|rgray[*]] [get_keepers $inst|rgray_s1[*]]
3159 logos_synchronizer [get_keepers $inst|wgray_s1[*]] [get_keepers $inst|wgray_s2[*]]
3160 logos_synchronizer [get_keepers $inst|rgray_s1[*]] [get_keepers $inst|rgray_s2[*]]
3161 logos_crossing [get_keepers $inst|mem*] [get_keepers $inst|rdata_q[*]]
3162 }
3163}
3164"##;
3165
3166const HAL_H: &str = r##"/* The device a host program drives the D5005 accelerator through: its MMIO words, buffers the accelerator reads and
3167 writes by I/O address, and its user clock. OPAE implements it on the card (logos_hal_opae.c), the card's PCI function
3168 driven directly implements it on a host whose devices reach memory at physical addresses (logos_hal_pci.c), and a
3169 simulation of the accelerator's RTL implements it through its simulator's bridge: Icarus Verilog's VPI
3170 (logos_hal_vpi.c) or Verilator's DPI-C (logos_hal_dpi.c). */
3171#ifndef LOGOS_HAL_H
3172#define LOGOS_HAL_H
3173
3174#include <stddef.h>
3175#include <stdint.h>
3176
3177typedef struct logos_dev logos_dev;
3178
3179/* Open the accelerator whose AFU ID is `afu_id` (the UUID text); NULL with a message on stderr if there is none. */
3180logos_dev *logos_open(const char *afu_id);
3181void logos_close(logos_dev *dev);
3182
3183uint64_t logos_mmio_read(logos_dev *dev, uint32_t byte_offset);
3184void logos_mmio_write(logos_dev *dev, uint32_t byte_offset, uint64_t value);
3185
3186/* A buffer of `bytes` the accelerator can read and write, 64-byte lines at the I/O address `*io_address`; NULL with a
3187 message on stderr if it cannot be had. */
3188void *logos_buffer(logos_dev *dev, size_t bytes, uint64_t *io_address);
3189void logos_buffer_free(logos_dev *dev, void *buffer);
3190
3191/* The clock the accelerator's core runs at, in Hz: the user clock, divided by the build's compute divide. */
3192double logos_user_clock_hz(logos_dev *dev);
3193
3194#endif
3195"##;
3196
3197const HAL_OPAE: &str = r##"/* The D5005 accelerator through OPAE 2.12 (libopae-c): the accelerator found by its AFU ID through whichever plugin
3198 serves it — the PF's port under the DFL driver, or on the OFS FIM PF0's VF1 bound to vfio-pci
3199 (scripts/d5005/afu-vf.sh) — MMIO region 0, buffers prepared and pinned by the driver or the IOMMU, the user clock as
3200 the FIM's user-clock PLL reports it, or for a port with no user-clock interface (a VF under VFIO) the one it was set
3201 to before it was handed over, LOGOS_USER_CLOCK_MHZ. */
3202#include "logos_d5005.h"
3203#include "logos_hal.h"
3204
3205#include <opae/fpga.h>
3206#include <stdio.h>
3207#include <stdlib.h>
3208#include <string.h>
3209
3210#define LOGOS_MAX_BUFFERS 64
3211
3212struct logos_dev {
3213 fpga_token token;
3214 fpga_handle handle;
3215 struct { void *ptr; uint64_t wsid; } buffers[LOGOS_MAX_BUFFERS];
3216};
3217
3218static int parse_guid(const char *text, fpga_guid guid) {
3219 int n = 0;
3220 for (const char *p = text; *p && n < 32; ++p) {
3221 int v;
3222 if (*p == '-') continue;
3223 if (*p >= '0' && *p <= '9') v = *p - '0';
3224 else if (*p >= 'a' && *p <= 'f') v = *p - 'a' + 10;
3225 else if (*p >= 'A' && *p <= 'F') v = *p - 'A' + 10;
3226 else return -1;
3227 if (n % 2 == 0) guid[n / 2] = (uint8_t)(v << 4);
3228 else guid[n / 2] |= (uint8_t)v;
3229 ++n;
3230 }
3231 return n == 32 ? 0 : -1;
3232}
3233
3234logos_dev *logos_open(const char *afu_id) {
3235 fpga_properties filter = NULL;
3236 fpga_guid guid;
3237 uint32_t found = 0;
3238 fpga_result r;
3239 logos_dev *dev = calloc(1, sizeof *dev);
3240 if (!dev) return NULL;
3241 if (parse_guid(afu_id, guid) != 0) {
3242 fprintf(stderr, "logos_open: %s is not a UUID\n", afu_id);
3243 free(dev);
3244 return NULL;
3245 }
3246 if ((r = fpgaGetProperties(NULL, &filter)) != FPGA_OK ||
3247 (r = fpgaPropertiesSetObjectType(filter, FPGA_ACCELERATOR)) != FPGA_OK ||
3248 (r = fpgaPropertiesSetGUID(filter, guid)) != FPGA_OK ||
3249 (r = fpgaEnumerate(&filter, 1, &dev->token, 1, &found)) != FPGA_OK) {
3250 fprintf(stderr, "logos_open: enumerating accelerators: %s\n", fpgaErrStr(r));
3251 fpgaDestroyProperties(&filter);
3252 free(dev);
3253 return NULL;
3254 }
3255 fpgaDestroyProperties(&filter);
3256 if (found == 0) {
3257 fprintf(stderr, "logos_open: no accelerator %s: is its image loaded (fpgaconf, or the FIM over JTAG) and its port on a "
3258 "function this user opens (the PF's port, or a VF bound to vfio-pci)?\n", afu_id);
3259 free(dev);
3260 return NULL;
3261 }
3262 if ((r = fpgaOpen(dev->token, &dev->handle, 0)) != FPGA_OK || (r = fpgaMapMMIO(dev->handle, 0, NULL)) != FPGA_OK) {
3263 fprintf(stderr, "logos_open: opening %s: %s\n", afu_id, fpgaErrStr(r));
3264 fpgaDestroyToken(&dev->token);
3265 free(dev);
3266 return NULL;
3267 }
3268 return dev;
3269}
3270
3271void logos_close(logos_dev *dev) {
3272 for (int i = 0; i < LOGOS_MAX_BUFFERS; ++i)
3273 if (dev->buffers[i].ptr) fpgaReleaseBuffer(dev->handle, dev->buffers[i].wsid);
3274 fpgaUnmapMMIO(dev->handle, 0);
3275 fpgaClose(dev->handle);
3276 fpgaDestroyToken(&dev->token);
3277 free(dev);
3278}
3279
3280uint64_t logos_mmio_read(logos_dev *dev, uint32_t byte_offset) {
3281 uint64_t v = 0;
3282 fpga_result r = fpgaReadMMIO64(dev->handle, 0, byte_offset, &v);
3283 if (r != FPGA_OK) {
3284 fprintf(stderr, "logos_mmio_read 0x%x: %s\n", byte_offset, fpgaErrStr(r));
3285 exit(3);
3286 }
3287 return v;
3288}
3289
3290void logos_mmio_write(logos_dev *dev, uint32_t byte_offset, uint64_t value) {
3291 fpga_result r = fpgaWriteMMIO64(dev->handle, 0, byte_offset, value);
3292 if (r != FPGA_OK) {
3293 fprintf(stderr, "logos_mmio_write 0x%x: %s\n", byte_offset, fpgaErrStr(r));
3294 exit(3);
3295 }
3296}
3297
3298void *logos_buffer(logos_dev *dev, size_t bytes, uint64_t *io_address) {
3299 for (int i = 0; i < LOGOS_MAX_BUFFERS; ++i) {
3300 if (dev->buffers[i].ptr) continue;
3301 void *ptr = NULL;
3302 uint64_t wsid = 0;
3303 fpga_result r = fpgaPrepareBuffer(dev->handle, bytes, &ptr, &wsid, 0);
3304 if (r == FPGA_OK) r = fpgaGetIOAddress(dev->handle, wsid, io_address);
3305 if (r != FPGA_OK) {
3306 fprintf(stderr, "logos_buffer %zu bytes: %s\n", bytes, fpgaErrStr(r));
3307 if (ptr) fpgaReleaseBuffer(dev->handle, wsid);
3308 return NULL;
3309 }
3310 dev->buffers[i].ptr = ptr;
3311 dev->buffers[i].wsid = wsid;
3312 return ptr;
3313 }
3314 fprintf(stderr, "logos_buffer: more than %d buffers\n", LOGOS_MAX_BUFFERS);
3315 return NULL;
3316}
3317
3318void logos_buffer_free(logos_dev *dev, void *buffer) {
3319 for (int i = 0; i < LOGOS_MAX_BUFFERS; ++i) {
3320 if (dev->buffers[i].ptr == buffer) {
3321 fpgaReleaseBuffer(dev->handle, dev->buffers[i].wsid);
3322 dev->buffers[i].ptr = NULL;
3323 }
3324 }
3325}
3326
3327double logos_user_clock_hz(logos_dev *dev) {
3328 uint64_t high = 0, low = 0;
3329 fpga_result r = fpgaGetUserClock(dev->handle, &high, &low, 0);
3330 if (r == FPGA_OK) return (double)high * 1e6 / LOGOS_D5005_COMPUTE_DIVIDE;
3331 const char *mhz = getenv("LOGOS_USER_CLOCK_MHZ");
3332 if (mhz && *mhz) return strtod(mhz, NULL) * 1e6 / LOGOS_D5005_COMPUTE_DIVIDE;
3333 fprintf(stderr, "logos_user_clock_hz: %s, and LOGOS_USER_CLOCK_MHZ is not set\n", fpgaErrStr(r));
3334 exit(3);
3335}
3336"##;
3337
3338const HAL_PCI: &str = r##"/* The D5005 accelerator driven through its PCI function directly, on a host whose devices reach its memory at
3339 physical addresses (no IOMMU, or the function in an identity domain), with no driver and no OPAE. The PR region's
3340 AFU is a VF of the FIM's PF (8086:bccf, PF0's VF1; scripts/d5005/afu-vf.sh attach enables the VFs and leaves them to
3341 no driver), found among the functions of LOGOS_PCI_DEVICES (default /sys/bus/pci/devices), or only LOGOS_D5005_VF
3342 when that is set, by the AFU ID its MMIO words 1 and 2 carry. BAR 0 is mapped from the function's resource0. Its command word decodes memory
3343 and masters the bus only while it is open, and gets back the value it had on close, at exit, and on SIGINT, SIGTERM
3344 and SIGHUP, so the accelerator never writes into memory the program no longer holds. Each buffer is one huge page
3345 (2 MiB, or 1 GiB past that, from the pages reserved in /sys/kernel/mm/hugepages); its I/O address is the page's
3346 physical address, read from /proc/self/pagemap and checked contiguous, and a buffer whose frames cannot be read is
3347 refused. The user clock is LOGOS_USER_CLOCK_MHZ, the one the port ran at before it was released. The BAR, the command
3348 word and the physical frames are root's. */
3349#define _GNU_SOURCE
3350#include "logos_d5005.h"
3351#include "logos_hal.h"
3352
3353#include <dirent.h>
3354#include <errno.h>
3355#include <fcntl.h>
3356#include <signal.h>
3357#include <stdio.h>
3358#include <stdlib.h>
3359#include <string.h>
3360#include <sys/mman.h>
3361#include <sys/stat.h>
3362#include <unistd.h>
3363
3364#ifndef MAP_HUGE_SHIFT
3365#define MAP_HUGE_SHIFT 26
3366#endif
3367
3368#define LOGOS_MAX_BUFFERS 64
3369#define LOGOS_PCI_COMMAND 4
3370#define LOGOS_PCI_MEMORY 0x0002u
3371#define LOGOS_PCI_BUS_MASTER 0x0004u
3372#define LOGOS_FRAME 4096ull
3373#define LOGOS_FRAME_PRESENT (1ull << 63)
3374#define LOGOS_FRAME_NUMBER ((1ull << 55) - 1)
3375
3376struct logos_dev {
3377 int config, resource;
3378 uint16_t command;
3379 volatile uint64_t *bar;
3380 size_t bar_bytes;
3381 struct { void *ptr; size_t bytes; } buffers[LOGOS_MAX_BUFFERS];
3382};
3383
3384/* The open function's configuration space and the command word it had, given back at exit or on a signal. */
3385static volatile sig_atomic_t held_config = -1;
3386static volatile sig_atomic_t held_command;
3387
3388static int command_read(int config, uint16_t *word) {
3389 uint8_t b[2];
3390 if (pread(config, b, 2, LOGOS_PCI_COMMAND) != 2) return -1;
3391 *word = (uint16_t)(b[0] | b[1] << 8);
3392 return 0;
3393}
3394
3395static int command_write(int config, uint16_t word) {
3396 uint8_t b[2] = {(uint8_t)word, (uint8_t)(word >> 8)};
3397 return pwrite(config, b, 2, LOGOS_PCI_COMMAND) == 2 ? 0 : -1;
3398}
3399
3400static void give_back(void) {
3401 int config = held_config;
3402 if (config < 0) return;
3403 held_config = -1;
3404 uint8_t b[2] = {(uint8_t)held_command, (uint8_t)(held_command >> 8)};
3405 ssize_t n = pwrite(config, b, 2, LOGOS_PCI_COMMAND);
3406 (void)n;
3407}
3408
3409static void on_signal(int sig) {
3410 give_back();
3411 raise(sig);
3412}
3413
3414static void hold(int config, uint16_t command) {
3415 static int armed = 0;
3416 held_command = command;
3417 held_config = config;
3418 if (armed) return;
3419 armed = 1;
3420 atexit(give_back);
3421 struct sigaction sa;
3422 memset(&sa, 0, sizeof sa);
3423 sa.sa_handler = on_signal;
3424 sa.sa_flags = SA_RESETHAND;
3425 sigemptyset(&sa.sa_mask);
3426 sigaction(SIGINT, &sa, NULL);
3427 sigaction(SIGTERM, &sa, NULL);
3428 sigaction(SIGHUP, &sa, NULL);
3429}
3430
3431static int parse_id(const char *text, uint64_t *high, uint64_t *low) {
3432 int n = 0;
3433 *high = *low = 0;
3434 for (const char *p = text; *p; ++p) {
3435 uint64_t v;
3436 if (*p == '-') continue;
3437 if (*p >= '0' && *p <= '9') v = (uint64_t)(*p - '0');
3438 else if (*p >= 'a' && *p <= 'f') v = (uint64_t)(*p - 'a' + 10);
3439 else if (*p >= 'A' && *p <= 'F') v = (uint64_t)(*p - 'A' + 10);
3440 else return -1;
3441 if (n == 32) return -1;
3442 if (n < 16) *high = *high << 4 | v;
3443 else *low = *low << 4 | v;
3444 ++n;
3445 }
3446 return n == 32 ? 0 : -1;
3447}
3448
3449/* `<dir>/<name>` into `out`; -1 if it does not fit. */
3450static int join(char *out, size_t size, const char *dir, const char *name) {
3451 int n = snprintf(out, size, "%s/%s", dir, name);
3452 return n >= 0 && (size_t)n < size ? 0 : -1;
3453}
3454
3455/* The text of `<dir>/<name>`, trailing whitespace dropped; -1 if there is no such file. */
3456static int read_text(const char *dir, const char *name, char *out, size_t size) {
3457 char path[4096];
3458 if (join(path, sizeof path, dir, name) != 0) return -1;
3459 FILE *f = fopen(path, "r");
3460 if (!f) return -1;
3461 size_t n = fread(out, 1, size - 1, f);
3462 fclose(f);
3463 while (n > 0 && (out[n - 1] == '\n' || out[n - 1] == ' ')) --n;
3464 out[n] = 0;
3465 return 0;
3466}
3467
3468/* Open `function` under `devices` as the accelerator if it is one this backend may drive and its MMIO words 1 and 2
3469 are the AFU ID: 1 open, 0 not the accelerator (its command word given back), -1 refused with a message. */
3470static int try_function(logos_dev *dev, const char *devices, const char *function, int pinned, uint64_t high, uint64_t low) {
3471 char dir[4096], text[256], path[4096];
3472 if (join(dir, sizeof dir, devices, function) != 0) return 0;
3473 if (!pinned) {
3474 char vendor[32], device[32];
3475 if (read_text(dir, "vendor", vendor, sizeof vendor) || read_text(dir, "device", device, sizeof device)) return 0;
3476 if (strcmp(vendor, "0x8086") != 0 || strcmp(device, "0xbccf") != 0) return 0;
3477 }
3478 char driver[4096];
3479 if (join(path, sizeof path, dir, "driver") != 0) return 0;
3480 ssize_t k = readlink(path, driver, sizeof driver - 1);
3481 if (k >= 0) {
3482 driver[k] = 0;
3483 const char *name = strrchr(driver, '/');
3484 fprintf(stderr, "logos_open: %s has the driver %s bound; this backend drives a function with none\n", function, name ? name + 1 : driver);
3485 return -1;
3486 }
3487 if (join(path, sizeof path, dir, "iommu_group") != 0) return 0;
3488 if (access(path, F_OK) == 0) {
3489 if (read_text(dir, "iommu_group/type", text, sizeof text) != 0) strcpy(text, "unreadable");
3490 if (strcmp(text, "identity") != 0) {
3491 fprintf(stderr, "logos_open: %s is behind an IOMMU that translates its addresses (its group's domain is %s): "
3492 "open it through VFIO (sw/logos_hal_opae.c)\n", function, text);
3493 return -1;
3494 }
3495 }
3496 if (join(path, sizeof path, dir, "config") != 0) return 0;
3497 int config = open(path, O_RDWR | O_CLOEXEC);
3498 uint16_t command = 0;
3499 if (config < 0 || command_read(config, &command) != 0) {
3500 fprintf(stderr, "logos_open: %s: %s (the command word is root's)\n", path, strerror(errno));
3501 if (config >= 0) close(config);
3502 return -1;
3503 }
3504 if (join(path, sizeof path, dir, "resource0") != 0) {
3505 close(config);
3506 return 0;
3507 }
3508 int resource = open(path, O_RDWR | O_SYNC | O_CLOEXEC);
3509 struct stat st;
3510 void *bar = MAP_FAILED;
3511 if (resource >= 0 && fstat(resource, &st) == 0 && st.st_size >= 24)
3512 bar = mmap(NULL, (size_t)st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, resource, 0);
3513 if (bar == MAP_FAILED) {
3514 fprintf(stderr, "logos_open: %s: %s (BAR 0 is root's)\n", path, strerror(errno));
3515 if (resource >= 0) close(resource);
3516 close(config);
3517 return -1;
3518 }
3519 if (command_write(config, (uint16_t)(command | LOGOS_PCI_MEMORY)) != 0) {
3520 fprintf(stderr, "logos_open: %s: writing the command word: %s\n", function, strerror(errno));
3521 munmap(bar, (size_t)st.st_size);
3522 close(resource);
3523 close(config);
3524 return -1;
3525 }
3526 volatile uint64_t *words = bar;
3527 if (words[2] != high || words[1] != low) {
3528 command_write(config, command);
3529 munmap(bar, (size_t)st.st_size);
3530 close(resource);
3531 close(config);
3532 return 0;
3533 }
3534 hold(config, command);
3535 if (command_write(config, (uint16_t)(command | LOGOS_PCI_MEMORY | LOGOS_PCI_BUS_MASTER)) != 0) {
3536 fprintf(stderr, "logos_open: %s: writing the command word: %s\n", function, strerror(errno));
3537 give_back();
3538 munmap(bar, (size_t)st.st_size);
3539 close(resource);
3540 close(config);
3541 return -1;
3542 }
3543 dev->config = config;
3544 dev->resource = resource;
3545 dev->command = command;
3546 dev->bar = words;
3547 dev->bar_bytes = (size_t)st.st_size;
3548 return 1;
3549}
3550
3551logos_dev *logos_open(const char *afu_id) {
3552 uint64_t high, low;
3553 if (parse_id(afu_id, &high, &low) != 0) {
3554 fprintf(stderr, "logos_open: %s is not a UUID\n", afu_id);
3555 return NULL;
3556 }
3557 logos_dev *dev = calloc(1, sizeof *dev);
3558 if (!dev) return NULL;
3559 const char *devices = getenv("LOGOS_PCI_DEVICES");
3560 if (!devices || !*devices) devices = "/sys/bus/pci/devices";
3561 const char *pinned = getenv("LOGOS_D5005_VF");
3562 if (pinned && *pinned) {
3563 if (try_function(dev, devices, pinned, 1, high, low) == 1) return dev;
3564 fprintf(stderr, "logos_open: no accelerator %s at %s\n", afu_id, pinned);
3565 free(dev);
3566 return NULL;
3567 }
3568 DIR *d = opendir(devices);
3569 if (!d) {
3570 fprintf(stderr, "logos_open: %s: %s\n", devices, strerror(errno));
3571 free(dev);
3572 return NULL;
3573 }
3574 for (struct dirent *e; (e = readdir(d)) != NULL;) {
3575 if (e->d_name[0] == '.') continue;
3576 if (try_function(dev, devices, e->d_name, 0, high, low) == 1) {
3577 closedir(d);
3578 return dev;
3579 }
3580 }
3581 closedir(d);
3582 fprintf(stderr, "logos_open: no accelerator %s on a VF of the FIM's PF (8086:bccf) with no driver bound: is its image "
3583 "configured, and the PF's VFs enabled (scripts/d5005/afu-vf.sh attach)?\n", afu_id);
3584 free(dev);
3585 return NULL;
3586}
3587
3588void logos_close(logos_dev *dev) {
3589 give_back();
3590 for (int i = 0; i < LOGOS_MAX_BUFFERS; ++i)
3591 if (dev->buffers[i].ptr) munmap(dev->buffers[i].ptr, dev->buffers[i].bytes);
3592 munmap((void *)dev->bar, dev->bar_bytes);
3593 close(dev->resource);
3594 close(dev->config);
3595 free(dev);
3596}
3597
3598static size_t word_of(logos_dev *dev, uint32_t byte_offset, const char *what) {
3599 if (byte_offset % 8 != 0 || (size_t)byte_offset + 8 > dev->bar_bytes) {
3600 fprintf(stderr, "%s 0x%x: not a word of the %zu-byte BAR\n", what, byte_offset, dev->bar_bytes);
3601 exit(3);
3602 }
3603 return byte_offset / 8;
3604}
3605
3606uint64_t logos_mmio_read(logos_dev *dev, uint32_t byte_offset) {
3607 return dev->bar[word_of(dev, byte_offset, "logos_mmio_read")];
3608}
3609
3610void logos_mmio_write(logos_dev *dev, uint32_t byte_offset, uint64_t value) {
3611 dev->bar[word_of(dev, byte_offset, "logos_mmio_write")] = value;
3612}
3613
3614/* The physical address of the `bytes` at `ptr`, frame by frame from the page map `pagemap` (an entry of 64 bits a
3615 4 KiB page of the address space: bit 63 present, bits 54..0 the frame): 0 if every frame is present, read and the
3616 one after the frame before it. */
3617static int frames(int pagemap, const void *ptr, size_t bytes, uint64_t *address) {
3618 uint64_t first = 0;
3619 for (size_t at = 0; at < bytes; at += LOGOS_FRAME) {
3620 uint64_t entry = 0;
3621 off_t where = (off_t)(((uintptr_t)ptr + at) / LOGOS_FRAME * 8);
3622 if (pread(pagemap, &entry, 8, where) != 8 || !(entry & LOGOS_FRAME_PRESENT) || (entry & LOGOS_FRAME_NUMBER) == 0) {
3623 fprintf(stderr, "logos_buffer: the page map gives no physical frame for this user (root reads them)\n");
3624 return -1;
3625 }
3626 if (at == 0) {
3627 first = entry & LOGOS_FRAME_NUMBER;
3628 } else if ((entry & LOGOS_FRAME_NUMBER) != first + at / LOGOS_FRAME) {
3629 fprintf(stderr, "logos_buffer: the huge page's frames are not contiguous\n");
3630 return -1;
3631 }
3632 }
3633 *address = first * LOGOS_FRAME;
3634 return 0;
3635}
3636
3637static int physical(const void *ptr, size_t bytes, uint64_t *address) {
3638 int fd = open("/proc/self/pagemap", O_RDONLY | O_CLOEXEC);
3639 if (fd < 0) {
3640 fprintf(stderr, "logos_buffer: /proc/self/pagemap: %s\n", strerror(errno));
3641 return -1;
3642 }
3643 int r = frames(fd, ptr, bytes, address);
3644 close(fd);
3645 return r;
3646}
3647
3648void *logos_buffer(logos_dev *dev, size_t bytes, uint64_t *io_address) {
3649 const size_t small = (size_t)2 << 20, large = (size_t)1 << 30;
3650 *io_address = 0;
3651 if (bytes == 0 || bytes > large) {
3652 fprintf(stderr, "logos_buffer %zu bytes: a buffer is one huge page, at most 1 GiB\n", bytes);
3653 return NULL;
3654 }
3655 int slot = -1;
3656 for (int i = 0; i < LOGOS_MAX_BUFFERS && slot < 0; ++i)
3657 if (!dev->buffers[i].ptr) slot = i;
3658 if (slot < 0) {
3659 fprintf(stderr, "logos_buffer: more than %d buffers\n", LOGOS_MAX_BUFFERS);
3660 return NULL;
3661 }
3662 size_t page = bytes <= small ? small : large;
3663 int shift = page == small ? 21 : 30;
3664 void *ptr = mmap(NULL, page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB | MAP_POPULATE | shift << MAP_HUGE_SHIFT,
3665 -1, 0);
3666 if (ptr == MAP_FAILED) {
3667 fprintf(stderr, "logos_buffer %zu bytes: no free %zu kB huge page (%s): reserve them in "
3668 "/sys/kernel/mm/hugepages/hugepages-%zukB/nr_hugepages\n", bytes, page >> 10, strerror(errno), page >> 10);
3669 return NULL;
3670 }
3671 uint64_t address = 0;
3672 if (physical(ptr, page, &address) != 0) {
3673 fprintf(stderr, "logos_buffer %zu bytes: refused, its I/O address unknown\n", bytes);
3674 munmap(ptr, page);
3675 return NULL;
3676 }
3677 dev->buffers[slot].ptr = ptr;
3678 dev->buffers[slot].bytes = page;
3679 *io_address = address;
3680 return ptr;
3681}
3682
3683void logos_buffer_free(logos_dev *dev, void *buffer) {
3684 for (int i = 0; i < LOGOS_MAX_BUFFERS; ++i) {
3685 if (dev->buffers[i].ptr == buffer) {
3686 munmap(buffer, dev->buffers[i].bytes);
3687 dev->buffers[i].ptr = NULL;
3688 }
3689 }
3690}
3691
3692double logos_user_clock_hz(logos_dev *dev) {
3693 (void)dev;
3694 const char *mhz = getenv("LOGOS_USER_CLOCK_MHZ");
3695 if (mhz && *mhz) return strtod(mhz, NULL) * 1e6 / LOGOS_D5005_COMPUTE_DIVIDE;
3696 fprintf(stderr, "logos_user_clock_hz: a VF has no user-clock interface, and LOGOS_USER_CLOCK_MHZ is not set\n");
3697 exit(3);
3698}
3699"##;
3700
3701const CORE_H: &str = r##"/* The bring-up core's commands as a host program rings them: the arguments, the doorbell, and the status record the core
3702 cuts when the command completes. A wait is bounded by the command's progress, not its length: the core reports a
3703 running command's progress while it moves (STATUS {tag, LOGOS_D5005_STATUS_RUNNING}, RESULT0 the count), and once
3704 LOGOS_CORE_MAX_POLLS register reads pass with no newer progress of this command than the wait last saw, the command
3705 is given up, its record {status 0xff, no tag} with the results of the last progress record it cut (none if it cut
3706 none: an engine's carries each context's job word), so a core that stops moving fails the program rather than
3707 hanging it, however long a moving command takes, and says where it stood. */
3708#ifndef LOGOS_CORE_H
3709#define LOGOS_CORE_H
3710
3711#include "logos_d5005.h"
3712#include "logos_hal.h"
3713
3714#ifndef LOGOS_CORE_MAX_POLLS
3715#define LOGOS_CORE_MAX_POLLS (1ull << 26)
3716#endif
3717
3718typedef struct {
3719 uint64_t status, result0, result1;
3720} logos_record;
3721
3722/* Ring `op` tagged `tag` with its three arguments once the command FIFO has room, and wait for its status record. */
3723static inline logos_record logos_core_run(logos_dev *dev, uint8_t op, uint64_t tag, uint64_t a0, uint64_t a1, uint64_t a2) {
3724 logos_record r = {0xff, 0, 0};
3725 uint64_t seq = logos_mmio_read(dev, LOGOS_D5005_REG_STATUS_SEQ), polls = 0, seen = 0;
3726 while (logos_mmio_read(dev, LOGOS_D5005_REG_CMD_SPACE) == 0) {
3727 if (++polls > LOGOS_CORE_MAX_POLLS) return r;
3728 }
3729 logos_mmio_write(dev, LOGOS_D5005_REG_ARG0, a0);
3730 logos_mmio_write(dev, LOGOS_D5005_REG_ARG1, a1);
3731 logos_mmio_write(dev, LOGOS_D5005_REG_ARG2, a2);
3732 logos_mmio_write(dev, LOGOS_D5005_REG_DOORBELL, (tag << 8) | op);
3733 polls = 0;
3734 while (logos_mmio_read(dev, LOGOS_D5005_REG_STATUS_SEQ) == seq) {
3735 if (++polls <= LOGOS_CORE_MAX_POLLS) continue;
3736 uint64_t status = logos_mmio_read(dev, LOGOS_D5005_REG_STATUS), at = logos_mmio_read(dev, LOGOS_D5005_REG_RESULT0);
3737 if (logos_mmio_read(dev, LOGOS_D5005_REG_STATUS_SEQ) != seq) break;
3738 if ((status & 0xff) != LOGOS_D5005_STATUS_RUNNING || status >> 8 != (tag & ((1ull << 56) - 1))) return r;
3739 if (at == seen) {
3740 r.result0 = at;
3741 r.result1 = logos_mmio_read(dev, LOGOS_D5005_REG_RESULT1);
3742 return r;
3743 }
3744 seen = at;
3745 polls = 0;
3746 }
3747 r.status = logos_mmio_read(dev, LOGOS_D5005_REG_STATUS);
3748 r.result0 = logos_mmio_read(dev, LOGOS_D5005_REG_RESULT0);
3749 r.result1 = logos_mmio_read(dev, LOGOS_D5005_REG_RESULT1);
3750 return r;
3751}
3752
3753#endif
3754"##;
3755
3756const LOAD: &str = r##"/* Load a weight image into the D5005's memory channels and prove it is there: the directory the image writer filled
3757 (`channel<k>.bin`, a channel's lines; `tiles.bin`, the tile table of decode_engine::image::Image::tile_table). Each
3758 channel is copied in through a host buffer at the channel's full line width, then read back and compared with its
3759 file line for line, and where the layout carries check words each tile's CRC-32C is checked as its lines come back.
3760 With --self-test one line of a tile is then damaged on the card, the verify must name that tile and no other, the
3761 tile is loaded again from its file, and the verify must pass again. One JSON object on stdout; the exit status is 0
3762 only when every check held, 2 when the image or the card cannot be had.
3763
3764 logos_load <image directory> [--self-test]
3765
3766 Built against the OPAE backend it runs on the card; against the VPI backend, inside a simulation of the RTL. */
3767#include "logos_core.h"
3768
3769#include <inttypes.h>
3770#include <stdio.h>
3771#include <stdlib.h>
3772#include <string.h>
3773
3774#ifndef LOGOS_PROGRAM_MAIN
3775#define LOGOS_PROGRAM_MAIN main
3776#endif
3777
3778/* Channel lines one copy moves. */
3779#define LOGOS_LOAD_CHUNK 4096u
3780#define LOGOS_MAX_BAD 64
3781
3782typedef struct {
3783 uint64_t channel, line, lines;
3784} tile;
3785
3786typedef struct {
3787 uint64_t line_bytes, check, channels, tiles;
3788 uint64_t *channel_lines;
3789 tile *tile;
3790} table;
3791
3792typedef struct {
3793 uint64_t bad[LOGOS_MAX_BAD], crc[LOGOS_MAX_BAD];
3794 int bad_n, crc_n;
3795 uint64_t cycles[LOGOS_D5005_BANKS];
3796} verdict;
3797
3798static int failures;
3799static uint64_t tag = 1;
3800
3801static void expect(int ok, const char *what) {
3802 if (!ok) {
3803 ++failures;
3804 fprintf(stderr, "logos_load: %s\n", what);
3805 }
3806}
3807
3808static logos_record run(logos_dev *dev, uint8_t op, uint64_t a0, uint64_t a1, uint64_t a2) {
3809 uint64_t t = tag++;
3810 logos_record r = logos_core_run(dev, op, t, a0, a1, a2);
3811 expect((r.status >> 8) == t && (r.status & 0xff) == LOGOS_D5005_STATUS_OK, "a copy does not complete");
3812 return r;
3813}
3814
3815/* CRC-32C (Castagnoli), the register carried across calls without its inversions. */
3816static uint32_t crc32c_update(uint32_t c, const uint8_t *p, size_t n) {
3817 for (size_t i = 0; i < n; ++i) {
3818 c ^= p[i];
3819 for (int k = 0; k < 8; ++k) c = (c >> 1) ^ (0x82F63B78u & (0u - (c & 1u)));
3820 }
3821 return c;
3822}
3823
3824static int read_words(FILE *f, uint64_t *w, size_t n) {
3825 uint8_t b[8];
3826 for (size_t i = 0; i < n; ++i) {
3827 if (fread(b, 1, 8, f) != 8) return -1;
3828 w[i] = 0;
3829 for (int k = 7; k >= 0; --k) w[i] = (w[i] << 8) | b[k];
3830 }
3831 return 0;
3832}
3833
3834static int read_table(const char *dir, table *t) {
3835 char path[4096], magic[8];
3836 uint64_t head[4];
3837 snprintf(path, sizeof path, "%s/tiles.bin", dir);
3838 FILE *f = fopen(path, "rb");
3839 if (!f) {
3840 fprintf(stderr, "logos_load: no %s\n", path);
3841 return -1;
3842 }
3843 int ok = fread(magic, 1, 8, f) == 8 && memcmp(magic, "LOGOSIMG", 8) == 0 && read_words(f, head, 4) == 0 && head[0] == 1;
3844 if (ok) {
3845 t->line_bytes = head[1];
3846 t->check = head[2];
3847 t->channels = head[3];
3848 t->channel_lines = calloc(t->channels, sizeof *t->channel_lines);
3849 ok = t->channel_lines && read_words(f, t->channel_lines, t->channels) == 0 && read_words(f, &t->tiles, 1) == 0;
3850 }
3851 if (ok) {
3852 t->tile = calloc(t->tiles ? t->tiles : 1, sizeof *t->tile);
3853 ok = t->tile != NULL;
3854 for (uint64_t i = 0; ok && i < t->tiles; ++i) ok = read_words(f, &t->tile[i].channel, 3) == 0;
3855 }
3856 fclose(f);
3857 if (!ok) fprintf(stderr, "logos_load: %s is not a tile table\n", path);
3858 return ok ? 0 : -1;
3859}
3860
3861static FILE *open_channel(const char *dir, uint64_t k) {
3862 char path[4096];
3863 snprintf(path, sizeof path, "%s/channel%" PRIu64 ".bin", dir, k);
3864 FILE *f = fopen(path, "rb");
3865 if (!f) fprintf(stderr, "logos_load: no %s\n", path);
3866 return f;
3867}
3868
3869/* Host lines `lines` channel lines take packed end to end. */
3870static uint64_t host_lines(uint64_t lines) {
3871 return (lines * LOGOS_D5005_MEM_LINE_BYTES + LOGOS_D5005_HOST_LINE_BYTES - 1) / LOGOS_D5005_HOST_LINE_BYTES;
3872}
3873
3874/* Copy channel `k`'s lines [first, first + lines) from its file onto the card; the cycles the copies took. */
3875static uint64_t load_lines(logos_dev *dev, uint8_t *buf, uint64_t io, FILE *f, uint64_t k, uint64_t first, uint64_t lines) {
3876 uint64_t cycles = 0;
3877 for (uint64_t at = first; at < first + lines; at += LOGOS_LOAD_CHUNK) {
3878 uint64_t n = first + lines - at < LOGOS_LOAD_CHUNK ? first + lines - at : LOGOS_LOAD_CHUNK;
3879 size_t bytes = (size_t)(n * LOGOS_D5005_MEM_LINE_BYTES);
3880 memset(buf, 0, (size_t)host_lines(n) * LOGOS_D5005_HOST_LINE_BYTES);
3881 expect(fseek(f, (long)(at * LOGOS_D5005_MEM_LINE_BYTES), SEEK_SET) == 0 && fread(buf, 1, bytes, f) == bytes,
3882 "a channel's file is short of its lines");
3883 cycles += run(dev, LOGOS_D5005_OP_HOST_TO_MEM_PACKED, io >> 6, k << 56 | at, n).result1;
3884 }
3885 return cycles;
3886}
3887
3888static void add(uint64_t *list, int *n, uint64_t v) {
3889 if (*n > 0 && list[*n - 1] == v) return;
3890 if (*n < LOGOS_MAX_BAD) list[(*n)++] = v;
3891}
3892
3893/* Read every channel back and hold it to its file and its tiles to their check words. */
3894static void verify(logos_dev *dev, const table *t, const char *dir, uint8_t *buf, uint64_t io, uint8_t *file, verdict *v) {
3895 memset(v, 0, sizeof *v);
3896 const uint64_t lb = LOGOS_D5005_MEM_LINE_BYTES;
3897 for (uint64_t k = 0; k < t->channels; ++k) {
3898 FILE *f = open_channel(dir, k);
3899 if (!f) {
3900 ++failures;
3901 continue;
3902 }
3903 uint64_t ti = 0, tile_end = 0;
3904 uint32_t crc = 0;
3905 int tile_bad = 0;
3906 while (ti < t->tiles && t->tile[ti].channel != k) ++ti;
3907 for (uint64_t at = 0; at < t->channel_lines[k]; at += LOGOS_LOAD_CHUNK) {
3908 uint64_t n = t->channel_lines[k] - at < LOGOS_LOAD_CHUNK ? t->channel_lines[k] - at : LOGOS_LOAD_CHUNK;
3909 v->cycles[k] += run(dev, LOGOS_D5005_OP_MEM_TO_HOST_PACKED, io >> 6, k << 56 | at, n).result1;
3910 expect(fseek(f, (long)(at * lb), SEEK_SET) == 0 && fread(file, 1, (size_t)(n * lb), f) == n * lb,
3911 "a channel's file is short of its lines");
3912 for (uint64_t i = 0; i < n; ++i) {
3913 uint64_t line = at + i;
3914 const uint8_t *got = buf + i * lb;
3915 if (ti < t->tiles && line == t->tile[ti].line) {
3916 tile_end = line + t->tile[ti].lines;
3917 crc = 0xFFFFFFFFu;
3918 tile_bad = 0;
3919 }
3920 if (memcmp(got, file + i * lb, (size_t)lb) != 0 && !tile_bad) {
3921 tile_bad = 1;
3922 add(v->bad, &v->bad_n, ti);
3923 }
3924 if (line + 1 < tile_end) {
3925 crc = crc32c_update(crc, got, (size_t)lb);
3926 } else if (ti < t->tiles && line + 1 == tile_end) {
3927 if (t->check) {
3928 crc = ~crc32c_update(crc, got, (size_t)(lb - 4));
3929 uint32_t word = (uint32_t)got[lb - 4] | (uint32_t)got[lb - 3] << 8 | (uint32_t)got[lb - 2] << 16 |
3930 (uint32_t)got[lb - 1] << 24;
3931 if (word != crc) add(v->crc, &v->crc_n, ti);
3932 }
3933 do ++ti;
3934 while (ti < t->tiles && t->tile[ti].channel != k);
3935 }
3936 }
3937 }
3938 fclose(f);
3939 }
3940}
3941
3942static void print_list(const char *name, const uint64_t *list, int n) {
3943 printf("\"%s\": [", name);
3944 for (int i = 0; i < n; ++i) printf("%s%" PRIu64, i ? ", " : "", list[i]);
3945 printf("]");
3946}
3947
3948int LOGOS_PROGRAM_MAIN(int argc, char **argv) {
3949 if (argc < 2) {
3950 fprintf(stderr, "usage: logos_load <image directory> [--self-test]\n");
3951 return 2;
3952 }
3953 const char *dir = argv[1];
3954 int self_test = argc > 2 && strcmp(argv[2], "--self-test") == 0;
3955 table t;
3956 if (read_table(dir, &t) != 0) return 2;
3957 if (t.line_bytes != LOGOS_D5005_MEM_LINE_BYTES || t.channels != LOGOS_D5005_BANKS) {
3958 fprintf(stderr, "logos_load: the image is %" PRIu64 " channels of %" PRIu64 "-byte lines, the accelerator %d of %d\n",
3959 t.channels, t.line_bytes, LOGOS_D5005_BANKS, LOGOS_D5005_MEM_LINE_BYTES);
3960 return 2;
3961 }
3962 logos_dev *dev = logos_open(LOGOS_D5005_AFU_ID);
3963 if (!dev) return 2;
3964 uint64_t io = 0;
3965 size_t bytes = (size_t)host_lines(LOGOS_LOAD_CHUNK) * LOGOS_D5005_HOST_LINE_BYTES;
3966 uint8_t *buf = logos_buffer(dev, bytes, &io), *file = malloc(bytes);
3967 if (!buf || !file) return 2;
3968
3969 printf("{\"image\": \"%s\", \"line_bytes\": %" PRIu64 ", \"check\": %s, \"channels\": [", dir, t.line_bytes,
3970 t.check ? "true" : "false");
3971 uint64_t load_cycles[LOGOS_D5005_BANKS] = {0};
3972 for (uint64_t k = 0; k < t.channels; ++k) {
3973 FILE *f = open_channel(dir, k);
3974 if (!f) return 2;
3975 load_cycles[k] = load_lines(dev, buf, io, f, k, 0, t.channel_lines[k]);
3976 fclose(f);
3977 }
3978 verdict v;
3979 verify(dev, &t, dir, buf, io, file, &v);
3980 for (uint64_t k = 0; k < t.channels; ++k)
3981 printf("%s{\"lines\": %" PRIu64 ", \"load_cycles\": %" PRIu64 ", \"verify_cycles\": %" PRIu64 "}", k ? ", " : "",
3982 t.channel_lines[k], load_cycles[k], v.cycles[k]);
3983 printf("], ");
3984 print_list("bad_tiles", v.bad, v.bad_n);
3985 printf(", ");
3986 print_list("crc_errors", v.crc, v.crc_n);
3987 expect(v.bad_n == 0 && v.crc_n == 0, "the image read back is not the image");
3988
3989 if (self_test && t.tiles > 0) {
3990 uint64_t victim = t.tiles / 2;
3991 const tile *x = &t.tile[victim];
3992 uint64_t line = x->line + x->lines / 2;
3993 run(dev, LOGOS_D5005_OP_MEM_TO_HOST_PACKED, io >> 6, x->channel << 56 | line, 1);
3994 buf[(line * 7) % LOGOS_D5005_MEM_LINE_BYTES] ^= 0x10;
3995 run(dev, LOGOS_D5005_OP_HOST_TO_MEM_PACKED, io >> 6, x->channel << 56 | line, 1);
3996 verdict damaged, reloaded;
3997 verify(dev, &t, dir, buf, io, file, &damaged);
3998 FILE *f = open_channel(dir, x->channel);
3999 if (!f) return 2;
4000 load_lines(dev, buf, io, f, x->channel, x->line, x->lines);
4001 fclose(f);
4002 verify(dev, &t, dir, buf, io, file, &reloaded);
4003 printf(", \"self_test\": {\"tile\": %" PRIu64 ", ", victim);
4004 print_list("bad_tiles", damaged.bad, damaged.bad_n);
4005 printf(", ");
4006 print_list("crc_errors", damaged.crc, damaged.crc_n);
4007 printf(", ");
4008 print_list("after_reload", reloaded.bad, reloaded.bad_n);
4009 printf("}");
4010 expect(damaged.bad_n == 1 && damaged.bad[0] == victim, "the damaged tile is not the one the verify names");
4011 expect(!t.check || (damaged.crc_n == 1 && damaged.crc[0] == victim), "the damaged tile's check word does not catch it");
4012 expect(reloaded.bad_n == 0 && reloaded.crc_n == 0, "the tile loaded again does not verify");
4013 }
4014 printf(", \"checks_failed\": %d}\n", failures);
4015 fflush(stdout);
4016 logos_buffer_free(dev, buf);
4017 free(file);
4018 logos_close(dev);
4019 return failures ? 1 : 0;
4020}
4021"##;
4022
4023const STEP: &str = r##"/* Run a model on the decode engine: the program `ofs::write_engine_program` wrote — the memory image, the engine's
4024 configuration and tables, the embedding table — loaded onto the card, then greedy decoding one token a step from a
4025 prompt, for each of up to the program's sequences at once, their prompts apart by `--`: each step every sequence's
4026 row of the row table (its region, its position, its ring row) and its token's embedding written into the engine,
4027 the step run over every row, and each sequence once past its prompt reading its own row's logits back, their
4028 arg-max its next token; a greedy program's engine takes the arg-max itself, and the row's read-back is that one beat.
4029 The sequences step together, so each is at the same position; one whose prompt and tokens are done runs on with the
4030 rest. Each step's position, first sequence's token and cycles on stderr as it completes; one JSON object on stdout
4031 once decoding ends: the first sequence's tokens, every sequence's, each step's cycles and the engine's and card
4032 port's faults. The exit status is 0 only when every step ran without one, 2 when the program or the card cannot be
4033 had, a program for more rows or a larger store than the build's among them.
4034
4035 logos_step [--inject] <program directory> <tokens to generate> <prompt token>... [-- <prompt token>...]...
4036
4037 With --inject, on SECDED lines, every line the image load writes carries one flipped bit, which every read of it
4038 corrects; the record then carries each channel's words corrected and lost. With LOGOS_PROFILE naming a job list
4039 (`d5005_afu --trace-store`'s jobs.txt, a "job <j> word <w> <kind>" line a job), the prompt's last position runs a job
4040 at a time, each job from its word to the next job's alone, its cycles on stdout as "<j> <kind> <cycles>" before the
4041 JSON; the step's record is its last job's. Built against the OPAE backend it runs on the card; against a simulation
4042 backend, inside the RTL. */
4043#include "logos_core.h"
4044
4045#include <inttypes.h>
4046#include <math.h>
4047#include <stdio.h>
4048#include <stdlib.h>
4049#include <string.h>
4050#include <fcntl.h>
4051#include <sys/mman.h>
4052#include <sys/stat.h>
4053#include <unistd.h>
4054
4055#ifndef LOGOS_PROGRAM_MAIN
4056#define LOGOS_PROGRAM_MAIN main
4057#endif
4058
4059/* Host lines one copy moves. */
4060#define LOGOS_STEP_CHUNK 4096u
4061/* The most sequences one run decodes. */
4062#define LOGOS_STEP_SEQUENCES 64
4063/* The sequencer contexts of the build: sequence s runs in context s mod the contexts, as its row s / the contexts. */
4064#ifndef LOGOS_D5005_CONTEXTS
4065#define LOGOS_D5005_CONTEXTS 1
4066#endif
4067/* Whether the build's f32 products are the card's floating-point DSP blocks'. */
4068#ifndef LOGOS_D5005_FP_HARD
4069#define LOGOS_D5005_FP_HARD 0
4070#endif
4071
4072typedef struct {
4073 uint64_t knobs, space, base, granule_log2, job_words, residual_beat, residual_beats, logits_beat, logits_beats;
4074 uint64_t tokens, hidden, vocab, ring_rows, regions, region, sequences, ctx, store_beats, greedy, contexts, fp_hard;
4075 uint64_t channel_lines[LOGOS_D5005_BANKS];
4076 char streaming[256];
4077} program;
4078
4079static int failures;
4080static uint64_t tag = 1;
4081
4082static void expect(int ok, const char *what) {
4083 if (!ok) {
4084 ++failures;
4085 fprintf(stderr, "logos_step: %s\n", what);
4086 }
4087}
4088
4089static logos_record run(logos_dev *dev, uint8_t op, uint64_t a0, uint64_t a1, uint64_t a2) {
4090 uint64_t t = tag++;
4091 logos_record r = logos_core_run(dev, op, t, a0, a1, a2);
4092 expect((r.status >> 8) == t && (r.status & 0xff) == LOGOS_D5005_STATUS_OK, "a command does not complete");
4093 return r;
4094}
4095
4096static int read_program(const char *dir, program *p) {
4097 char path[4096], key[64];
4098 snprintf(path, sizeof path, "%s/engine.txt", dir);
4099 FILE *f = fopen(path, "r");
4100 if (!f) return -1;
4101 memset(p, 0, sizeof *p);
4102 int fields = 0;
4103 while (fscanf(f, "%63s", key) == 1) {
4104 uint64_t *one = NULL, *two = NULL;
4105 if (!strcmp(key, "knobs")) one = &p->knobs;
4106 else if (!strcmp(key, "space")) one = &p->space;
4107 else if (!strcmp(key, "base")) one = &p->base;
4108 else if (!strcmp(key, "granule_log2")) one = &p->granule_log2;
4109 else if (!strcmp(key, "job_words")) one = &p->job_words;
4110 else if (!strcmp(key, "residual")) { one = &p->residual_beat; two = &p->residual_beats; }
4111 else if (!strcmp(key, "logits")) { one = &p->logits_beat; two = &p->logits_beats; }
4112 else if (!strcmp(key, "tokens")) one = &p->tokens;
4113 else if (!strcmp(key, "hidden")) one = &p->hidden;
4114 else if (!strcmp(key, "vocab")) one = &p->vocab;
4115 else if (!strcmp(key, "ring_rows")) one = &p->ring_rows;
4116 else if (!strcmp(key, "regions")) one = &p->regions;
4117 else if (!strcmp(key, "region")) one = &p->region;
4118 else if (!strcmp(key, "sequences")) one = &p->sequences;
4119 else if (!strcmp(key, "ctx")) one = &p->ctx;
4120 else if (!strcmp(key, "store")) one = &p->store_beats;
4121 else if (!strcmp(key, "greedy")) {
4122 if (fscanf(f, "%" SCNu64, &p->greedy) != 1 || p->greedy != 1) { fclose(f); return -1; }
4123 continue;
4124 } else if (!strcmp(key, "fp")) {
4125 char unit[8];
4126 if (fscanf(f, "%7s", unit) != 1 || strcmp(unit, "hard")) { fclose(f); return -1; }
4127 p->fp_hard = 1;
4128 continue;
4129 } else if (!strcmp(key, "contexts")) {
4130 if (fscanf(f, "%" SCNu64, &p->contexts) != 1 || p->contexts < 2) { fclose(f); return -1; }
4131 continue;
4132 } else if (!strcmp(key, "channels")) {
4133 for (int k = 0; k < LOGOS_D5005_BANKS; ++k)
4134 if (fscanf(f, "%" SCNu64, &p->channel_lines[k]) != 1) { fclose(f); return -1; }
4135 ++fields;
4136 continue;
4137 } else if (!strcmp(key, "streaming")) {
4138 if (fscanf(f, "%255s", p->streaming) != 1) { fclose(f); return -1; }
4139 continue;
4140 } else { fclose(f); return -1; }
4141 if (fscanf(f, "%" SCNu64, one) != 1 || (two && fscanf(f, "%" SCNu64, two) != 1)) { fclose(f); return -1; }
4142 ++fields;
4143 }
4144 fclose(f);
4145 if (!p->contexts) p->contexts = 1;
4146 return fields == 17 && p->ring_rows > 0 && p->hidden <= p->residual_beats * 16 && p->logits_beats >= 1 &&
4147 (p->greedy || p->vocab <= p->logits_beats * 16)
4148 ? 0
4149 : -1;
4150}
4151
4152/* A file's bytes, its length in `n`. */
4153static uint8_t *slurp(const char *dir, const char *name, size_t *n) {
4154 char path[4096];
4155 snprintf(path, sizeof path, "%s/%s", dir, name);
4156 FILE *f = fopen(path, "rb");
4157 if (!f) return NULL;
4158 fseek(f, 0, SEEK_END);
4159 long len = ftell(f);
4160 fseek(f, 0, SEEK_SET);
4161 uint8_t *b = malloc(len > 0 ? (size_t)len : 1);
4162 *n = len > 0 && b && fread(b, 1, (size_t)len, f) == (size_t)len ? (size_t)len : 0;
4163 fclose(f);
4164 if (len > 0 && *n == 0) { free(b); return NULL; }
4165 return b;
4166}
4167
4168/* A file mapped read-only, its length in `n`: the memory image and the embeddings are larger than a host's memory may
4169 be, so they are read as the card takes them. */
4170static const uint8_t *map_file(const char *dir, const char *name, size_t *n) {
4171 char path[4096];
4172 snprintf(path, sizeof path, "%s/%s", dir, name);
4173 *n = 0;
4174 int fd = open(path, O_RDONLY);
4175 if (fd < 0) return NULL;
4176 struct stat st;
4177 if (fstat(fd, &st) || st.st_size <= 0) {
4178 close(fd);
4179 return NULL;
4180 }
4181 void *m = mmap(NULL, (size_t)st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
4182 close(fd);
4183 if (m == MAP_FAILED) return NULL;
4184 *n = (size_t)st.st_size;
4185 return m;
4186}
4187
4188/* `lines` host lines of `bytes` into the engine's table `table` from entry `first`, through the host buffer. */
4189static void table_write(logos_dev *dev, uint8_t *buf, uint64_t io, int table, uint64_t first, const uint8_t *bytes, uint64_t lines) {
4190 for (uint64_t at = 0; at < lines; at += LOGOS_STEP_CHUNK) {
4191 uint64_t n = lines - at < LOGOS_STEP_CHUNK ? lines - at : LOGOS_STEP_CHUNK;
4192 memcpy(buf, bytes + at * 64, (size_t)n * 64);
4193 run(dev, LOGOS_D5005_OP_ENGINE_TABLE, io >> 6, (uint64_t)table << 32 | (first + at), n);
4194 }
4195}
4196
4197/* The prompt's last position run a job at a time from the job list at `path`: each job from its word to the next's
4198 runs alone and its cycles go to stdout. */
4199static logos_record profiled(logos_dev *dev, const char *path, uint64_t job_words) {
4200 FILE *f = fopen(path, "r");
4201 if (!f) {
4202 fprintf(stderr, "logos_step: no job list %s\n", path);
4203 exit(2);
4204 }
4205 static uint64_t word[4096];
4206 static char kind[4096][24];
4207 uint64_t n = 0, j, w;
4208 char line[65536], k[24];
4209 while (fgets(line, sizeof line, f) && n < 4096)
4210 if (sscanf(line, "job %" SCNu64 " word %" SCNu64 " %23[A-Za-z]", &j, &w, k) == 3) {
4211 word[n] = w;
4212 strcpy(kind[n], k);
4213 ++n;
4214 }
4215 fclose(f);
4216 logos_record r = {0, 0, 0};
4217 for (uint64_t i = 1; i < n; ++i) {
4218 uint64_t to = i + 1 < n ? word[i + 1] : job_words;
4219 if (to <= word[i]) continue;
4220 r = run(dev, LOGOS_D5005_OP_ENGINE_RUN, word[i], to, 0);
4221 if (r.result0 & 3) fprintf(stderr, "logos_step: a fault at job %" PRIu64 "\n", i);
4222 printf("%" PRIu64 " %s %" PRIu64 "\n", i, kind[i], r.result1);
4223 }
4224 fflush(stdout);
4225 return r;
4226}
4227
4228/* The first strictly largest logit that is a number, as numerics::argmax picks it. */
4229static uint64_t argmax(const float *x, uint64_t n) {
4230 uint64_t best = 0;
4231 int found = 0;
4232 for (uint64_t i = 0; i < n; ++i)
4233 if (!isnan(x[i]) && (!found || x[i] > x[best])) { best = i; found = 1; }
4234 return best;
4235}
4236
4237int LOGOS_PROGRAM_MAIN(int argc, char **argv) {
4238 int inject = argc > 1 && !strcmp(argv[1], "--inject");
4239 if (inject) {
4240 --argc;
4241 ++argv;
4242 }
4243 if (argc < 4 || (inject && !LOGOS_D5005_SECDED)) {
4244 fprintf(stderr, "usage: logos_step [--inject] <program directory> <tokens to generate> <prompt token>... [-- <prompt token>...]..., --inject on SECDED lines\n");
4245 return 2;
4246 }
4247 const char *dir = argv[1];
4248 uint64_t generate = strtoull(argv[2], NULL, 0);
4249 /* The sequences' prompts: each from argv[first[s]], prompt_n[s] tokens. */
4250 uint64_t seqs = 0, first[LOGOS_STEP_SEQUENCES], prompt_n[LOGOS_STEP_SEQUENCES];
4251 for (int a = 3; a <= argc; ++a) {
4252 if (a == argc || !strcmp(argv[a], "--")) {
4253 uint64_t start = seqs ? first[seqs - 1] + prompt_n[seqs - 1] + 1 : 3;
4254 if (seqs == LOGOS_STEP_SEQUENCES || a == (int)start) {
4255 fprintf(stderr, "logos_step: every sequence needs a prompt, at most %d sequences\n", LOGOS_STEP_SEQUENCES);
4256 return 2;
4257 }
4258 first[seqs] = start;
4259 prompt_n[seqs] = (uint64_t)a - start;
4260 ++seqs;
4261 }
4262 }
4263 program p;
4264 if (read_program(dir, &p)) {
4265 fprintf(stderr, "logos_step: %s/engine.txt is no engine program\n", dir);
4266 return 2;
4267 }
4268 if (strcmp(p.streaming, LOGOS_D5005_STREAMING)) {
4269 fprintf(stderr, "logos_step: %s is a program for the streaming unit \"%s\", this build's is \"%s\"\n", dir, p.streaming,
4270 LOGOS_D5005_STREAMING);
4271 return 2;
4272 }
4273 uint64_t longest = 0;
4274 for (uint64_t s = 0; s < seqs; ++s) longest = prompt_n[s] > longest ? prompt_n[s] : longest;
4275 if (longest + generate > p.ctx) {
4276 fprintf(stderr, "logos_step: %" PRIu64 " tokens past the %" PRIu64 " positions a sequence reaches\n", longest + generate, p.ctx);
4277 return 2;
4278 }
4279 if (seqs > p.sequences) {
4280 fprintf(stderr, "logos_step: %" PRIu64 " sequences past the program's %" PRIu64 "\n", seqs, p.sequences);
4281 return 2;
4282 }
4283 if (p.contexts != LOGOS_D5005_CONTEXTS) {
4284 fprintf(stderr, "logos_step: %s is a program of %" PRIu64 " contexts, this build runs %u\n", dir, p.contexts,
4285 (unsigned)LOGOS_D5005_CONTEXTS);
4286 return 2;
4287 }
4288 if (p.fp_hard != LOGOS_D5005_FP_HARD) {
4289 fprintf(stderr, "logos_step: %s is a program for %s products, this build's are %s\n", dir,
4290 p.fp_hard ? "the card's floating-point blocks'" : "the engine's own", LOGOS_D5005_FP_HARD ? "the card's floating-point blocks'" : "the engine's own");
4291 return 2;
4292 }
4293 const uint64_t C = LOGOS_D5005_CONTEXTS;
4294 if ((p.sequences + C - 1) / C > LOGOS_D5005_ROWS || p.store_beats > LOGOS_D5005_STORE_BEATS) {
4295 fprintf(stderr, "logos_step: %s steps %" PRIu64 " rows over a store of %" PRIu64 " beats, past this build's %u rows and %u beats\n",
4296 dir, (p.sequences + C - 1) / C, p.store_beats, LOGOS_D5005_ROWS, LOGOS_D5005_STORE_BEATS);
4297 return 2;
4298 }
4299 size_t embed_n = 0;
4300 const uint8_t *embed = map_file(dir, "embed.bin", &embed_n);
4301 if (!embed || embed_n != p.tokens * p.hidden * 4) {
4302 fprintf(stderr, "logos_step: embed.bin is not %" PRIu64 " embeddings of %" PRIu64 " elements\n", p.tokens, p.hidden);
4303 return 2;
4304 }
4305 logos_dev *dev = logos_open(LOGOS_D5005_AFU_ID);
4306 if (!dev) return 2;
4307 uint64_t io = 0, buf_lines = LOGOS_STEP_CHUNK > p.logits_beats ? LOGOS_STEP_CHUNK : p.logits_beats;
4308 uint8_t *buf = logos_buffer(dev, (size_t)buf_lines * 64, &io);
4309 if (!buf) return 2;
4310
4311 /* Context c's rows, the sequences s with s mod C = c: context 0's at arg0[15:0], context c's at arg0[32 + 16(c - 1) +: 16]. */
4312 uint64_t config = p.knobs << 16;
4313 for (uint64_t c = 0; c < C; ++c) config |= ((seqs + C - 1 - c) / C) << (c ? 32 + 16 * (c - 1) : 0);
4314 run(dev, LOGOS_D5005_OP_ENGINE_CONFIG, config, p.space, p.base | p.granule_log2 << 56);
4315 static const struct { int table; const char *file; } tables[] = {
4316 {0, "table0.bin"}, {2, "table2.bin"}, {3, "table3.bin"}, {4, "table4.bin"}, {5, "table5.bin"}};
4317 for (size_t k = 0; k < sizeof tables / sizeof tables[0]; ++k) {
4318 size_t n = 0;
4319 uint8_t *bytes = slurp(dir, tables[k].file, &n);
4320 if (!bytes || n % 64) {
4321 fprintf(stderr, "logos_step: %s is not whole 64-byte entries\n", tables[k].file);
4322 return 2;
4323 }
4324 table_write(dev, buf, io, tables[k].table, 0, bytes, n / 64);
4325 free(bytes);
4326 }
4327 if (inject)
4328 for (int k = 0; k < LOGOS_D5005_BANKS; ++k)
4329 run(dev, LOGOS_D5005_OP_INJECT, (uint64_t)1 << k, 5u << 16 | 5u, p.channel_lines[k]);
4330 for (int k = 0; k < LOGOS_D5005_BANKS; ++k) {
4331 char name[32];
4332 size_t n = 0;
4333 snprintf(name, sizeof name, "channel%d.bin", k);
4334 const uint8_t *bytes = p.channel_lines[k] ? map_file(dir, name, &n) : NULL;
4335 if (n != p.channel_lines[k] * LOGOS_D5005_MEM_LINE_BYTES || (n && !bytes)) {
4336 fprintf(stderr, "logos_step: %s is not %" PRIu64 " lines\n", name, p.channel_lines[k]);
4337 return 2;
4338 }
4339 for (uint64_t at = 0; at < p.channel_lines[k]; at += LOGOS_STEP_CHUNK) {
4340 uint64_t lines = p.channel_lines[k] - at < LOGOS_STEP_CHUNK ? p.channel_lines[k] - at : LOGOS_STEP_CHUNK;
4341 memcpy(buf, bytes + at * LOGOS_D5005_MEM_LINE_BYTES, (size_t)lines * LOGOS_D5005_MEM_LINE_BYTES);
4342 run(dev, LOGOS_D5005_OP_HOST_TO_MEM, io >> 6, (uint64_t)k << 56 | at, lines);
4343 }
4344 if (bytes) munmap((void *)bytes, n);
4345 }
4346
4347 uint64_t total = longest + generate, ran = 0;
4348 uint64_t *cycles = calloc((size_t)total, sizeof *cycles), *tokens = calloc((size_t)(seqs * total), sizeof *tokens);
4349 uint64_t token[LOGOS_STEP_SEQUENCES];
4350 for (uint64_t s = 0; s < seqs; ++s) token[s] = strtoull(argv[first[s]], NULL, 0);
4351 uint8_t *rows = calloc((size_t)seqs, 64), *e = calloc((size_t)p.residual_beats, 64);
4352 int faults = 0;
4353 for (uint64_t pos = 0; pos < total; ++pos) {
4354 for (uint64_t s = 0; s < seqs; ++s) {
4355 if (token[s] >= p.tokens) {
4356 fprintf(stderr, "logos_step: token %" PRIu64 " past the embedding table\n", token[s]);
4357 return 2;
4358 }
4359 tokens[s * total + pos] = token[s];
4360 /* Each context's rows in its own run of the buffer: sequence s at row s / C of context s mod C. */
4361 uint64_t region = p.regions + s * p.region, ring = pos % p.ring_rows, at = (s % C) * ((seqs + C - 1) / C) + s / C;
4362 memcpy(rows + 64 * at, ®ion, 4);
4363 memcpy(rows + 64 * at + 4, &pos, 4);
4364 rows[64 * at + 8] = (uint8_t)ring;
4365 }
4366 ran = pos + 1;
4367 for (uint64_t c = 0; c < C; ++c)
4368 if ((seqs + C - 1 - c) / C)
4369 table_write(dev, buf, io, 1, c << 56, rows + 64 * c * ((seqs + C - 1) / C), (seqs + C - 1 - c) / C);
4370 for (uint64_t s = 0; s < seqs; ++s) {
4371 memset(e, 0, (size_t)p.residual_beats * 64);
4372 memcpy(e, embed + token[s] * p.hidden * 4, (size_t)p.hidden * 4);
4373 table_write(dev, buf, io, 6, (s % C) << 56 | (p.residual_beat + s / C * p.residual_beats), e, p.residual_beats);
4374 }
4375 const char *profile = getenv("LOGOS_PROFILE");
4376 logos_record step = profile && pos + 1 == longest ? profiled(dev, profile, p.job_words)
4377 : run(dev, LOGOS_D5005_OP_ENGINE_RUN, 0, p.job_words, 0);
4378 cycles[pos] = step.result1;
4379 fprintf(stderr, "logos_step: position %" PRIu64 " of %" PRIu64 ", token %" PRIu64 ": %" PRIu64 " cycles\n", pos, total,
4380 token[0], step.result1);
4381 if ((step.status & 0xff) == 0xff) {
4382 ++faults;
4383 if (C == 1)
4384 fprintf(stderr, "logos_step: position %" PRIu64 ": the engine stopped moving at word %" PRIu64 "\n", pos, step.result0 >> 32);
4385 else
4386 for (uint64_t c = 0; c < C; ++c)
4387 fprintf(stderr, "logos_step: position %" PRIu64 ": the engine stopped moving; context %" PRIu64 " at word %" PRIu64 "\n", pos, c,
4388 step.result0 >> (32 + 16 * c) & 0xffff);
4389 break;
4390 }
4391 if (step.result0 & 3) {
4392 ++faults;
4393 if (C == 1)
4394 fprintf(stderr, "logos_step: position %" PRIu64 ": the %s faulted at word %" PRIu64 "\n", pos,
4395 step.result0 & 1 ? "engine" : "card port", step.result0 >> 32);
4396 else
4397 for (uint64_t c = 0; c < C; ++c)
4398 fprintf(stderr, "logos_step: position %" PRIu64 ": the %s faulted; context %" PRIu64 " reached word %" PRIu64 "\n", pos,
4399 step.result0 & 1 ? "engine" : "card port", c, step.result0 >> (32 + 16 * c) & 0xffff);
4400 break;
4401 }
4402 for (uint64_t s = 0; s < seqs; ++s) {
4403 if (pos + 1 < prompt_n[s]) {
4404 token[s] = strtoull(argv[first[s] + pos + 1], NULL, 0);
4405 } else if (pos + 1 < total) {
4406 run(dev, LOGOS_D5005_OP_STORE_READ, io >> 6, (s % C) << 56 | (p.logits_beat + s / C * p.logits_beats), p.greedy ? 1 : p.logits_beats);
4407 token[s] = p.greedy ? ((const uint32_t *)buf)[0] : argmax((const float *)buf, p.vocab);
4408 }
4409 }
4410 }
4411 printf("{\"tokens\": [");
4412 for (uint64_t pos = 0; pos < ran && pos < prompt_n[0] + generate; ++pos) printf("%s%" PRIu64, pos ? ", " : "", tokens[pos]);
4413 printf("], \"sequences\": [");
4414 for (uint64_t s = 0; s < seqs; ++s) {
4415 printf("%s[", s ? ", " : "");
4416 for (uint64_t pos = 0; pos < ran && pos < prompt_n[s] + generate; ++pos) printf("%s%" PRIu64, pos ? ", " : "", tokens[s * total + pos]);
4417 printf("]");
4418 }
4419 printf("], \"prompt\": %" PRIu64 ", \"cycles\": [", prompt_n[0]);
4420 for (uint64_t pos = 0; pos < total; ++pos) printf("%s%" PRIu64, pos ? ", " : "", cycles[pos]);
4421 printf("], \"faults\": %d", faults);
4422 if (inject) {
4423 printf(", \"secded\": [");
4424 for (int k = 0; k < LOGOS_D5005_BANKS; ++k) {
4425 logos_record c = run(dev, LOGOS_D5005_OP_ECC_COUNTS, (uint64_t)k, 0, 0);
4426 printf("%s{\"corrected\": %" PRIu64 ", \"uncorrectable\": %" PRIu64 "}", k ? ", " : "", c.result0 & 0xffffffffu, c.result0 >> 32);
4427 }
4428 printf("]");
4429 }
4430 printf(", \"checks_failed\": %d}\n", failures);
4431 fflush(stdout);
4432 free(cycles);
4433 free(tokens);
4434 free(rows);
4435 free(e);
4436 munmap((void *)embed, embed_n);
4437 logos_buffer_free(dev, buf);
4438 logos_close(dev);
4439 return failures || faults ? 1 : 0;
4440}
4441"##;
4442
4443const RECEIPT: &str = r##"/* The bring-up receipt of the D5005 accelerator: the accelerator identifies itself, every memory channel is filled and
4444 checked alone and all four at once (each at the core's cycle count, so its bandwidth), a flipped seed is seen, and a
4445 buffer of host memory makes the round trip through a channel unchanged. On SECDED lines every channel then proves its
4446 code: no word corrected or lost before a flip is injected, a flipped data bit and a flipped check bit corrected on
4447 every line, two flips in one word found uncorrectable on every line, a flip in each of two words both corrected, and
4448 a clean fill clean again; without SECDED the core refuses the code's two ops, and without the engine the engine's
4449 four. The receipt is one JSON object on stdout; the exit status is 0 only when every check held.
4450
4451 logos_receipt [lines per channel] [host lines]
4452
4453 Built against the OPAE backend it runs on the card; against the VPI backend, inside a simulation of the RTL. */
4454#include "logos_core.h"
4455
4456#include <inttypes.h>
4457#include <stdio.h>
4458#include <stdlib.h>
4459#include <string.h>
4460
4461#ifndef LOGOS_PROGRAM_MAIN
4462#define LOGOS_PROGRAM_MAIN main
4463#endif
4464
4465typedef logos_record record;
4466
4467static int failures;
4468
4469static void expect(int ok, const char *what) {
4470 if (!ok) {
4471 ++failures;
4472 fprintf(stderr, "logos_receipt: %s\n", what);
4473 }
4474}
4475
4476/* Ring `op` with its arguments and wait for its status record. */
4477static record run(logos_dev *dev, uint8_t op, uint64_t tag, uint64_t a0, uint64_t a1, uint64_t a2) {
4478 record r = logos_core_run(dev, op, tag, a0, a1, a2);
4479 expect((r.status >> 8) == tag, "a status record of another command");
4480 return r;
4481}
4482
4483static double gbps(uint64_t lines, uint64_t cycles, double hz) {
4484 return cycles ? (double)lines * LOGOS_D5005_MEM_LINE_BYTES * hz / (double)cycles / 1e9 : 0.0;
4485}
4486
4487#if LOGOS_D5005_SECDED
4488/* Channel `b`'s words corrected and words found uncorrectable since reset. */
4489static void secded_counts(logos_dev *dev, uint64_t *tag, int b, uint64_t *fixed, uint64_t *lost) {
4490 record r = run(dev, LOGOS_D5005_OP_ECC_COUNTS, (*tag)++, (uint64_t)b, 0, 0);
4491 expect((r.status & 0xff) == LOGOS_D5005_STATUS_OK, "the SECDED counts cannot be read");
4492 *fixed = r.result0 & UINT64_C(0xffffffff);
4493 *lost = r.result0 >> 32;
4494}
4495
4496/* The code on channel `b`: `n` lines filled with bus bits `a` and `c` flipped (none when `a` is negative), checked,
4497 and the lines that differ and the words corrected and lost since the last case held to what the case expects of
4498 every line. */
4499static void secded_case(logos_dev *dev, uint64_t *tag, int b, uint64_t n, const char *name, int a, int c, uint64_t wrong,
4500 uint64_t fixed, uint64_t lost, uint64_t *fixed_at, uint64_t *lost_at) {
4501 uint64_t size = ((uint64_t)0x5ec << 32) | n, f, l;
4502 if (a >= 0) {
4503 record inject = run(dev, LOGOS_D5005_OP_INJECT, (*tag)++, (uint64_t)1 << b, (uint64_t)c << 16 | (uint64_t)a, n);
4504 expect((inject.status & 0xff) == LOGOS_D5005_STATUS_OK, "a flip cannot be injected");
4505 }
4506 record fill = run(dev, LOGOS_D5005_OP_FILL, (*tag)++, (uint64_t)1 << b, 0, size);
4507 record check = run(dev, LOGOS_D5005_OP_CHECK, (*tag)++, (uint64_t)1 << b, 0, size);
4508 expect((fill.status & 0xff) == LOGOS_D5005_STATUS_OK && (check.status & 0xff) == LOGOS_D5005_STATUS_OK, "a SECDED fill or check fails");
4509 secded_counts(dev, tag, b, &f, &l);
4510 char what[96];
4511 snprintf(what, sizeof what, "channel %d, %s: the lines that differ", b, name);
4512 expect(check.result0 == wrong * n, what);
4513 snprintf(what, sizeof what, "channel %d, %s: the words corrected and lost", b, name);
4514 expect(f - *fixed_at == fixed * n && l - *lost_at == lost * n, what);
4515 printf(", \"%s\": {\"mismatches\": %" PRIu64 ", \"corrected\": %" PRIu64 ", \"uncorrectable\": %" PRIu64 "}", name,
4516 check.result0, f - *fixed_at, l - *lost_at);
4517 *fixed_at = f;
4518 *lost_at = l;
4519}
4520#endif
4521
4522int LOGOS_PROGRAM_MAIN(int argc, char **argv) {
4523 uint64_t lines = argc > 1 ? strtoull(argv[1], NULL, 0) : 1u << 20;
4524 uint64_t host_lines = argc > 2 ? strtoull(argv[2], NULL, 0) : 1u << 12;
4525 uint64_t tag = 1;
4526 logos_dev *dev = logos_open(LOGOS_D5005_AFU_ID);
4527 if (!dev) return 2;
4528 double hz = logos_user_clock_hz(dev);
4529 uint64_t dfh = logos_mmio_read(dev, LOGOS_D5005_REG_DFH);
4530 uint64_t id_l = logos_mmio_read(dev, LOGOS_D5005_REG_ID_L), id_h = logos_mmio_read(dev, LOGOS_D5005_REG_ID_H);
4531 char id[40];
4532 snprintf(id, sizeof id, "%08" PRIx64 "-%04" PRIx64 "-%04" PRIx64 "-%04" PRIx64 "-%012" PRIx64, id_h >> 32,
4533 (id_h >> 16) & 0xffff, id_h & 0xffff, id_l >> 48, id_l & UINT64_C(0xffffffffffff));
4534 expect((dfh >> 60) == 1 && ((dfh >> 40) & 1), "the feature header is no AFU's at the end of the list");
4535 expect(strcmp(id, LOGOS_D5005_AFU_ID) == 0, "the AFU ID is not the one opened");
4536 logos_mmio_write(dev, LOGOS_D5005_REG_SCRATCH, 0x5a5aa5a5f00dcafeull);
4537 expect(logos_mmio_read(dev, LOGOS_D5005_REG_SCRATCH) == 0x5a5aa5a5f00dcafeull, "the scratch register does not hold");
4538 record nop = run(dev, LOGOS_D5005_OP_NOP, tag++, 0, 0, 0);
4539 expect((nop.status & 0xff) == LOGOS_D5005_STATUS_OK, "a NOP does not complete");
4540
4541 printf("{\"afu_id\": \"%s\", \"build\": %" PRIu64 ", \"user_clock_hz\": %.0f, \"line_bytes\": %d, \"channels\": [",
4542 id, logos_mmio_read(dev, LOGOS_D5005_REG_BUILD_ID), hz, LOGOS_D5005_MEM_LINE_BYTES);
4543 for (int b = 0; b <= LOGOS_D5005_BANKS; ++b) {
4544 uint64_t mask = b < LOGOS_D5005_BANKS ? 1u << b : (1u << LOGOS_D5005_BANKS) - 1;
4545 uint64_t seed = 0x1000 + (uint64_t)b, size = (seed << 32) | lines;
4546 record fill = run(dev, LOGOS_D5005_OP_FILL, tag++, mask, 0, size);
4547 record check = run(dev, LOGOS_D5005_OP_CHECK, tag++, mask, 0, size);
4548 record wrong = run(dev, LOGOS_D5005_OP_CHECK, tag++, mask, 0, ((seed + 1) << 32) | lines);
4549 int channels = b < LOGOS_D5005_BANKS ? 1 : LOGOS_D5005_BANKS;
4550 expect((fill.status & 0xff) == LOGOS_D5005_STATUS_OK && (check.status & 0xff) == LOGOS_D5005_STATUS_OK, "a fill or check fails");
4551 expect(check.result0 == 0, "lines read back differ from those written");
4552 expect(wrong.result0 == lines * (uint64_t)channels, "a check under another seed does not differ on every line");
4553 printf("%s{\"mask\": %" PRIu64 ", \"lines\": %" PRIu64 ", \"fill_cycles\": %" PRIu64 ", \"check_cycles\": %" PRIu64
4554 ", \"mismatches\": %" PRIu64 ", \"write_gb_per_s\": %.3f, \"read_gb_per_s\": %.3f}",
4555 b ? ", " : "", mask, lines, fill.result1, check.result1, check.result0,
4556 gbps(lines * (uint64_t)channels, fill.result1, hz), gbps(lines * (uint64_t)channels, check.result1, hz));
4557 }
4558 printf("]");
4559
4560#if LOGOS_D5005_SECDED
4561 printf(", \"secded\": [");
4562 for (int b = 0; b < LOGOS_D5005_BANKS; ++b) {
4563 uint64_t n = lines < 64 ? lines : 64, fixed, lost;
4564 int data = 64 * 3 + 5, other = 64 * 3 + 58, check_bit = (int)LOGOS_D5005_SECDED_CHECK_BIT(3, 3), far = 64 * 6 + 1;
4565 secded_counts(dev, &tag, b, &fixed, &lost);
4566 expect(fixed == 0 && lost == 0, "a channel corrected or lost words before a flip was injected");
4567 printf("%s{\"channel\": %d, \"lines\": %" PRIu64 ", \"before\": {\"corrected\": %" PRIu64 ", \"uncorrectable\": %" PRIu64 "}",
4568 b ? ", " : "", b, n, fixed, lost);
4569 secded_case(dev, &tag, b, n, "data_bit", data, data, 0, 1, 0, &fixed, &lost);
4570 secded_case(dev, &tag, b, n, "check_bit", check_bit, check_bit, 0, 1, 0, &fixed, &lost);
4571 secded_case(dev, &tag, b, n, "two_bits_one_word", data, other, 1, 0, 1, &fixed, &lost);
4572 secded_case(dev, &tag, b, n, "two_words", data, far, 0, 2, 0, &fixed, &lost);
4573 secded_case(dev, &tag, b, n, "clean", -1, -1, 0, 0, 0, &fixed, &lost);
4574 printf("}");
4575 }
4576 printf("]");
4577#else
4578 record inject = run(dev, LOGOS_D5005_OP_INJECT, tag++, 0, 0, 0);
4579 record counts = run(dev, LOGOS_D5005_OP_ECC_COUNTS, tag++, 0, 0, 0);
4580 expect((inject.status & 0xff) == LOGOS_D5005_STATUS_UNKNOWN_OP && (counts.status & 0xff) == LOGOS_D5005_STATUS_UNKNOWN_OP,
4581 "a core without SECDED lines runs the code's ops");
4582#endif
4583#if !LOGOS_D5005_ENGINE
4584 {
4585 static const uint8_t engine_ops[] = {LOGOS_D5005_OP_ENGINE_CONFIG, LOGOS_D5005_OP_ENGINE_TABLE, LOGOS_D5005_OP_ENGINE_RUN,
4586 LOGOS_D5005_OP_STORE_READ};
4587 for (size_t k = 0; k < sizeof engine_ops; ++k)
4588 expect((run(dev, engine_ops[k], tag++, 0, 0, 0).status & 0xff) == LOGOS_D5005_STATUS_UNKNOWN_OP,
4589 "a core without the engine runs the engine's ops");
4590 }
4591#endif
4592
4593 uint64_t io_in = 0, io_out = 0;
4594 size_t bytes = (size_t)host_lines * LOGOS_D5005_HOST_LINE_BYTES;
4595 uint8_t *in = logos_buffer(dev, bytes, &io_in), *out = logos_buffer(dev, bytes, &io_out);
4596 if (!in || !out) return 2;
4597 for (size_t i = 0; i < bytes; ++i) in[i] = (uint8_t)(i * 2654435761u >> 13);
4598 memset(out, 0, bytes);
4599 uint64_t at = (uint64_t)1 << 56 | (lines & ((1u << 27) - 1));
4600 record to_mem = run(dev, LOGOS_D5005_OP_HOST_TO_MEM, tag++, io_in >> 6, at, host_lines);
4601 record to_host = run(dev, LOGOS_D5005_OP_MEM_TO_HOST, tag++, io_out >> 6, at, host_lines);
4602 int same = memcmp(in, out, bytes) == 0;
4603 expect((to_mem.status & 0xff) == LOGOS_D5005_STATUS_OK && (to_host.status & 0xff) == LOGOS_D5005_STATUS_OK, "a host copy fails");
4604 expect(same, "host lines do not come back unchanged through a channel");
4605 printf(", \"host\": {\"lines\": %" PRIu64 ", \"to_mem_cycles\": %" PRIu64 ", \"to_host_cycles\": %" PRIu64
4606 ", \"round_trip\": %s}", host_lines, to_mem.result1, to_host.result1, same ? "true" : "false");
4607 record unknown = run(dev, 0x7f, tag++, 0, 0, 0);
4608 expect((unknown.status & 0xff) == LOGOS_D5005_STATUS_UNKNOWN_OP, "an unknown op is not refused");
4609 expect(logos_mmio_read(dev, LOGOS_D5005_REG_ERRORS) == 0, "the accelerator reports an error");
4610 printf(", \"checks_failed\": %d}\n", failures);
4611 fflush(stdout);
4612 logos_buffer_free(dev, in);
4613 logos_buffer_free(dev, out);
4614 logos_close(dev);
4615 return failures ? 1 : 0;
4616}
4617"##;
4618
4619const HAL_SIM: &str = r##"/* The D5005 accelerator's RTL as a logos_dev, the part every simulator shares: the host program runs in a thread of
4620 its own, each of its MMIO accesses carried out on the shell's MMIO port at the falling edges of pClk (`sim_mmio`),
4621 and the simulation's host memory the program's own buffers at the I/O addresses `logos_buffer` gave them
4622 (`host_bytes`). The simulation advances only while the program waits on an MMIO access, so a run's every cycle
4623 count is the same on every machine. A backend includes this once and calls `sim_start`, `sim_mmio` and
4624 `host_bytes` from its simulator's hooks: logos_hal_vpi.c for Icarus Verilog, logos_hal_dpi.c for Verilator. */
4625#ifndef LOGOS_HAL_SIM_H
4626#define LOGOS_HAL_SIM_H
4627
4628#include "logos_hal.h"
4629
4630#include <pthread.h>
4631#include <stdio.h>
4632#include <stdlib.h>
4633#include <string.h>
4634
4635#define LOGOS_MAX_BUFFERS 64
4636#define LOGOS_IO_BASE (1ull << 32)
4637#define LOGOS_IO_ALIGN (1ull << 21)
4638
4639int logos_program_main(int argc, char **argv);
4640
4641enum { IDLE, WANT_READ, WANT_WRITE, READ_ISSUED, AWAIT_DATA, WRITE_ISSUED, DONE };
4642
4643static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
4644static pthread_cond_t wake = PTHREAD_COND_INITIALIZER;
4645static int state = IDLE, finished, exit_status, started;
4646static uint32_t req_word;
4647static uint64_t req_value;
4648static double user_clock_hz = 450e6;
4649
4650static int program_argc;
4651static char **program_argv;
4652
4653static struct { uint64_t io; size_t bytes; uint8_t *ptr; } buffers[LOGOS_MAX_BUFFERS];
4654static uint64_t next_io = LOGOS_IO_BASE;
4655
4656struct logos_dev { int open; };
4657static struct logos_dev the_device;
4658
4659/* What the MMIO port carries until the next falling edge. */
4660static struct { uint32_t address; int read, write; uint64_t wdata; } bus;
4661
4662logos_dev *logos_open(const char *afu_id) {
4663 (void)afu_id;
4664 the_device.open = 1;
4665 return &the_device;
4666}
4667
4668void logos_close(logos_dev *dev) { dev->open = 0; }
4669
4670static uint64_t bus_access(int want, uint32_t byte_offset, uint64_t value) {
4671 pthread_mutex_lock(&lock);
4672 req_word = byte_offset / 8;
4673 req_value = value;
4674 state = want;
4675 pthread_cond_broadcast(&wake);
4676 while (state != DONE) pthread_cond_wait(&wake, &lock);
4677 value = req_value;
4678 state = IDLE;
4679 pthread_cond_broadcast(&wake);
4680 pthread_mutex_unlock(&lock);
4681 return value;
4682}
4683
4684uint64_t logos_mmio_read(logos_dev *dev, uint32_t byte_offset) {
4685 (void)dev;
4686 return bus_access(WANT_READ, byte_offset, 0);
4687}
4688
4689void logos_mmio_write(logos_dev *dev, uint32_t byte_offset, uint64_t value) {
4690 (void)dev;
4691 bus_access(WANT_WRITE, byte_offset, value);
4692}
4693
4694void *logos_buffer(logos_dev *dev, size_t bytes, uint64_t *io_address) {
4695 (void)dev;
4696 for (int i = 0; i < LOGOS_MAX_BUFFERS; ++i) {
4697 if (buffers[i].ptr) continue;
4698 size_t rounded = (bytes + 4095) & ~(size_t)4095;
4699 buffers[i].ptr = aligned_alloc(4096, rounded);
4700 if (!buffers[i].ptr) return NULL;
4701 buffers[i].io = next_io;
4702 buffers[i].bytes = bytes;
4703 next_io += (rounded + LOGOS_IO_ALIGN - 1) & ~(LOGOS_IO_ALIGN - 1);
4704 *io_address = buffers[i].io;
4705 return buffers[i].ptr;
4706 }
4707 fprintf(stderr, "logos_buffer: more than %d buffers\n", LOGOS_MAX_BUFFERS);
4708 return NULL;
4709}
4710
4711void logos_buffer_free(logos_dev *dev, void *buffer) {
4712 (void)dev;
4713 for (int i = 0; i < LOGOS_MAX_BUFFERS; ++i) {
4714 if (buffers[i].ptr == buffer) {
4715 free(buffers[i].ptr);
4716 buffers[i].ptr = NULL;
4717 }
4718 }
4719}
4720
4721double logos_user_clock_hz(logos_dev *dev) {
4722 (void)dev;
4723 return user_clock_hz;
4724}
4725
4726/* The byte of host memory at I/O address `io`, `len` bytes of it inside one buffer; NULL for an address no buffer
4727 holds, which the simulation stops on, as an IOMMU fault would stop the card. */
4728static uint8_t *host_bytes(uint64_t io, size_t len) {
4729 for (int i = 0; i < LOGOS_MAX_BUFFERS; ++i)
4730 if (buffers[i].ptr && io >= buffers[i].io && io + len <= buffers[i].io + buffers[i].bytes) return buffers[i].ptr + (io - buffers[i].io);
4731 return NULL;
4732}
4733
4734static void *program(void *unused) {
4735 (void)unused;
4736 int status = logos_program_main(program_argc, program_argv);
4737 fflush(stdout);
4738 pthread_mutex_lock(&lock);
4739 finished = 1;
4740 exit_status = status;
4741 pthread_cond_broadcast(&wake);
4742 pthread_mutex_unlock(&lock);
4743 return NULL;
4744}
4745
4746/* Start the host program, the core at `core_period_ps`, its arguments those of `argv` that are +arg=<argument>, every
4747 one of them in order. */
4748static void sim_start(double core_period_ps, int argc, char **argv) {
4749 user_clock_hz = 1e12 / core_period_ps;
4750 int given = 0;
4751 for (int i = 0; i < argc; ++i) given += strncmp(argv[i], "+arg=", 5) == 0;
4752 program_argv = calloc((size_t)given + 2, sizeof *program_argv);
4753 if (!program_argv) {
4754 fprintf(stderr, "logos_hal_sim: no memory for the program's %d arguments\n", given);
4755 exit(2);
4756 }
4757 program_argc = 0;
4758 program_argv[program_argc++] = "logos_program";
4759 for (int i = 0; i < argc; ++i)
4760 if (strncmp(argv[i], "+arg=", 5) == 0) program_argv[program_argc++] = argv[i] + 5;
4761 program_argv[program_argc] = NULL;
4762 pthread_t t;
4763 started = 1;
4764 pthread_create(&t, NULL, program, NULL);
4765 pthread_detach(t);
4766}
4767
4768/* This process's command line, the whole of /proc/self/cmdline however long, split into its arguments; `*argc` their
4769 count. A simulator that keeps no argument list of its own hands the program its arguments from it. */
4770char **sim_cmdline(int *argc) {
4771 FILE *f = fopen("/proc/self/cmdline", "rb");
4772 size_t cap = 1 << 12, n = 0, got;
4773 char *text = malloc(cap + 1);
4774 if (!f || !text) {
4775 perror("logos_hal_sim: /proc/self/cmdline");
4776 exit(2);
4777 }
4778 while ((got = fread(text + n, 1, cap - n, f)) > 0) {
4779 n += got;
4780 if (n == cap && !(text = realloc(text, (cap *= 2) + 1))) {
4781 perror("logos_hal_sim: /proc/self/cmdline");
4782 exit(2);
4783 }
4784 }
4785 fclose(f);
4786 text[n] = 0;
4787 int count = 0;
4788 for (size_t i = 0; i < n; i += strlen(text + i) + 1) ++count;
4789 char **all = calloc((size_t)count + 1, sizeof *all);
4790 if (!all) {
4791 perror("logos_hal_sim: /proc/self/cmdline");
4792 exit(2);
4793 }
4794 count = 0;
4795 for (size_t i = 0; i < n; i += strlen(text + i) + 1) all[count++] = text + i;
4796 *argc = count;
4797 return all;
4798}
4799
4800/* At a falling edge of pClk, the read data the port shows `valid`/`data`: finish the access on the bus, then wait for
4801 the program's next one (or its end) and set `bus` to put it on the bus. 1 once the program has exited with no
4802 access outstanding. */
4803static int sim_mmio(int valid, uint64_t data) {
4804 int over = 0;
4805 pthread_mutex_lock(&lock);
4806 if (state == READ_ISSUED) {
4807 bus.read = 0;
4808 state = AWAIT_DATA;
4809 }
4810 if (state == WRITE_ISSUED) {
4811 bus.write = 0;
4812 state = DONE;
4813 pthread_cond_broadcast(&wake);
4814 }
4815 if (state == AWAIT_DATA && valid) {
4816 req_value = data;
4817 state = DONE;
4818 pthread_cond_broadcast(&wake);
4819 }
4820 while ((state == IDLE || state == DONE) && !finished) pthread_cond_wait(&wake, &lock);
4821 if (state == WANT_READ) {
4822 bus.address = req_word;
4823 bus.read = 1;
4824 state = READ_ISSUED;
4825 } else if (state == WANT_WRITE) {
4826 bus.address = req_word;
4827 bus.wdata = req_value;
4828 bus.write = 1;
4829 state = WRITE_ISSUED;
4830 } else if (finished && state == IDLE) {
4831 over = 1;
4832 }
4833 pthread_mutex_unlock(&lock);
4834 return over;
4835}
4836
4837#endif
4838"##;
4839
4840const HAL_VPI: &str = r##"/* The D5005 accelerator's RTL as a logos_dev, for Icarus Verilog's VPI (logos_hal_sim.h the rest): cosim's
4841 `$logos_program` starts the program, `$logos_mmio` carries its accesses on the MMIO port at every falling edge of
4842 pClk, and `$logos_host_read` and `$logos_host_write` are host memory.
4843
4844 Build: iverilog-vpi --name=logos_cosim -DLOGOS_PROGRAM_MAIN=logos_program_main logos_hal_vpi.c <program>.c */
4845#include "logos_hal_sim.h"
4846
4847#include <vpi_user.h>
4848
4849static vpiHandle arg(vpiHandle *it) { return vpi_scan(*it); }
4850
4851static uint64_t get_u64(vpiHandle h) {
4852 s_vpi_value v = {vpiVectorVal, {0}};
4853 vpi_get_value(h, &v);
4854 int words = (vpi_get(vpiSize, h) + 31) / 32;
4855 uint64_t lo = (uint32_t)v.value.vector[0].aval;
4856 uint64_t hi = words > 1 ? (uint32_t)v.value.vector[1].aval : 0;
4857 return lo | hi << 32;
4858}
4859
4860static void put_u64(vpiHandle h, uint64_t x) {
4861 s_vpi_vecval vec[2] = {{(PLI_INT32)(uint32_t)x, 0}, {(PLI_INT32)(uint32_t)(x >> 32), 0}};
4862 s_vpi_value v;
4863 v.format = vpiVectorVal;
4864 v.value.vector = vec;
4865 vpi_put_value(h, &v, NULL, vpiNoDelay);
4866}
4867
4868/* $logos_program(core_period_ps): start the host program, its arguments the plusargs +arg=<argument> in order. */
4869static PLI_INT32 program_calltf(PLI_BYTE8 *unused) {
4870 (void)unused;
4871 vpiHandle call = vpi_handle(vpiSysTfCall, NULL), it = vpi_iterate(vpiArgument, call);
4872 s_vpi_vlog_info info;
4873 double period = (double)get_u64(arg(&it));
4874 vpi_free_object(it);
4875 vpi_get_vlog_info(&info);
4876 sim_start(period, info.argc, info.argv);
4877 return 0;
4878}
4879
4880/* $logos_mmio(readdatavalid, readdata, address, read, write, writedata), at every falling edge of pClk. */
4881static PLI_INT32 mmio_calltf(PLI_BYTE8 *unused) {
4882 (void)unused;
4883 vpiHandle call = vpi_handle(vpiSysTfCall, NULL), it = vpi_iterate(vpiArgument, call);
4884 vpiHandle valid = arg(&it), data = arg(&it), address = arg(&it), read = arg(&it), write = arg(&it), wdata = arg(&it);
4885 vpi_free_object(it);
4886 int over = sim_mmio((int)get_u64(valid), get_u64(data));
4887 put_u64(address, bus.address);
4888 put_u64(read, (uint64_t)bus.read);
4889 put_u64(write, (uint64_t)bus.write);
4890 put_u64(wdata, bus.wdata);
4891 if (over) {
4892 vpi_printf("logos_cosim: the program exited %d\n", exit_status);
4893 vpi_control(vpiFinish, 1);
4894 }
4895 return 0;
4896}
4897
4898/* $logos_host_read(line, data) and $logos_host_write(line, data): one 64-byte line of host memory. */
4899static PLI_INT32 host_calltf(PLI_BYTE8 *write) {
4900 vpiHandle call = vpi_handle(vpiSysTfCall, NULL), it = vpi_iterate(vpiArgument, call);
4901 vpiHandle line = arg(&it), data = arg(&it);
4902 vpi_free_object(it);
4903 uint64_t io = get_u64(line) << 6;
4904 uint8_t *bytes = host_bytes(io, 64);
4905 if (!bytes) {
4906 vpi_printf("logos_cosim: host %s of I/O address 0x%llx, which no buffer holds\n", write ? "write" : "read", (unsigned long long)io);
4907 vpi_control(vpiFinish, 1);
4908 return 0;
4909 }
4910 s_vpi_vecval vec[16];
4911 s_vpi_value v;
4912 v.format = vpiVectorVal;
4913 if (write) {
4914 v.value.vector = NULL;
4915 vpi_get_value(data, &v);
4916 for (int w = 0; w < 16; ++w) {
4917 uint32_t x = (uint32_t)v.value.vector[w].aval;
4918 memcpy(bytes + 4 * w, &x, 4);
4919 }
4920 } else {
4921 for (int w = 0; w < 16; ++w) {
4922 uint32_t x;
4923 memcpy(&x, bytes + 4 * w, 4);
4924 vec[w].aval = (PLI_INT32)x;
4925 vec[w].bval = 0;
4926 }
4927 v.value.vector = vec;
4928 vpi_put_value(data, &v, NULL, vpiNoDelay);
4929 }
4930 return 0;
4931}
4932
4933static void register_task(const char *name, PLI_INT32 (*calltf)(PLI_BYTE8 *), PLI_BYTE8 *data) {
4934 s_vpi_systf_data tf = {vpiSysTask, 0, (PLI_BYTE8 *)name, calltf, NULL, NULL, data};
4935 vpi_register_systf(&tf);
4936}
4937
4938static void register_all(void) {
4939 register_task("$logos_program", program_calltf, NULL);
4940 register_task("$logos_mmio", mmio_calltf, NULL);
4941 register_task("$logos_host_read", host_calltf, NULL);
4942 register_task("$logos_host_write", host_calltf, (PLI_BYTE8 *)"w");
4943}
4944
4945void (*vlog_startup_routines[])(void) = {register_all, NULL};
4946"##;
4947
4948const HAL_DPI: &str = r##"/* The D5005 accelerator's RTL as a logos_dev, for Verilator's DPI-C (logos_hal_sim.h the rest): cosim built with
4949 LOGOS_DPI calls `logos_dpi_program` to start the program, `logos_dpi_mmio` at every falling edge of pClk to carry
4950 its accesses on the MMIO port, and `logos_dpi_host_read` and `logos_dpi_host_write` for host memory. The program's
4951 arguments are the simulation's own +arg=<argument> plusargs, read from the process's command line.
4952
4953 Build: cc -c -I<verilator root>/include/vltstd -DLOGOS_PROGRAM_MAIN=logos_program_main logos_hal_dpi.c <program>.c,
4954 the objects linked into the Verilated cosim. */
4955#include "logos_hal_sim.h"
4956
4957#include <svdpi.h>
4958
4959void logos_dpi_program(long long core_period_ps) {
4960 int argc;
4961 char **argv = sim_cmdline(&argc);
4962 sim_start((double)core_period_ps, argc, argv);
4963}
4964
4965int logos_dpi_mmio(svBit valid, long long data, int *address, svBit *read, svBit *write, long long *wdata) {
4966 int over = sim_mmio(valid, (uint64_t)data);
4967 *address = (int)bus.address;
4968 *read = (svBit)bus.read;
4969 *write = (svBit)bus.write;
4970 *wdata = (long long)bus.wdata;
4971 return over;
4972}
4973
4974int logos_dpi_exit_status(void) { return exit_status; }
4975
4976int logos_dpi_host_read(long long line, svBitVecVal *data) {
4977 uint8_t *bytes = host_bytes((uint64_t)line << 6, 64);
4978 if (!bytes) return 1;
4979 memcpy(data, bytes, 64);
4980 return 0;
4981}
4982
4983int logos_dpi_host_write(long long line, const svBitVecVal *data) {
4984 uint8_t *bytes = host_bytes((uint64_t)line << 6, 64);
4985 if (!bytes) return 1;
4986 memcpy(bytes, data, 64);
4987 return 0;
4988}
4989"##;
4990
4991const COSIM_TEMPLATE: &str = r##"// The accelerator's shell with a host program attached: the program (through logos_hal_vpi under Icarus Verilog, or
4993// logos_hal_dpi under Verilator with LOGOS_DPI defined) drives the MMIO port and owns host memory, four memory
4994// channels run on four unrelated clocks near 300 MHz, the host channel at 250 MHz, the shell at the user clock, its
4995// compute side stepping at every DIVIDE-th edge and the host program told that step's period; behind
4996// each channel the AFU configuration's channel model (with DDR4_LOG set, a DDR4-timed channel logs what it schedules).
4997// Run with the VPI module, vvp -M <dir> -m logos_cosim cosim, or the Verilated binary, either with
4998// [+arg=<the program's argument>]... (scripts/d5005/run-cosim.sh).
4999`timescale 1ps/1ps
5000module logos_d5005_cosim;
5001 parameter NB = 4, AW = 27, DWM = {mem_bits}, ECC = {ecc}, MBW = 7, HAW = 51, DWH = 512, HBW = 7, MAW = 15;
5002 parameter [63:0] AFU_ID_L = 64'h{id_l}, AFU_ID_H = 64'h{id_h}, BUILD = 64'h{build};
5003 parameter UCLK_PS = {uclk_ps};
5004 parameter DIVIDE = {divide};
5005 parameter DDR4_LOG = 0;
5006 reg pclk = 0, uclk = 0, rst_n = 0;
5007 // A channel clock a register, each its own process: Verilator takes no edge of one bit of a vector.
5008 reg mem_clk0 = 0, mem_clk1 = 0, mem_clk2 = 0, mem_clk3 = 0;
5009 wire [NB-1:0] mem_clk = {mem_clk3, mem_clk2, mem_clk1, mem_clk0};
5010 always #2000 pclk = ~pclk;
5011 always #(UCLK_PS / 2) uclk = ~uclk;
5012{uclk2_clock} always #1667 mem_clk0 = ~mem_clk0;
5013 initial begin #431 forever #1671 mem_clk1 = ~mem_clk1; end
5014 initial begin #977 forever #1663 mem_clk2 = ~mem_clk2; end
5015 initial begin #1301 forever #1669 mem_clk3 = ~mem_clk3; end
5016
5017 reg [MAW-1:0] mmio_address = 0;
5018 reg mmio_read = 0, mmio_write = 0;
5019 reg [63:0] mmio_writedata = 0;
5020 wire mmio_waitrequest, mmio_readdatavalid;
5021 wire [63:0] mmio_readdata;
5022 wire [HAW-1:0] host_rd_address, host_wr_address;
5023 wire host_rd_read, host_wr_write, host_rd_waitrequest, host_rd_readdatavalid, host_wr_waitrequest, host_wr_writeresponsevalid;
5024 wire [HBW-1:0] host_rd_burstcount, host_wr_burstcount;
5025 wire [DWH-1:0] host_rd_readdata, host_wr_writedata;
5026 wire [DWH/8-1:0] host_wr_byteenable;
5027 wire [NB*AW-1:0] mem_address;
5028 wire [NB-1:0] mem_read, mem_write, mem_waitrequest, mem_readdatavalid;
5029 wire [NB*MBW-1:0] mem_burstcount;
5030 wire [NB*DWM-1:0] mem_writedata, mem_readdata;
5031 wire [NB*DWM/8-1:0] mem_byteenable;
5032
5033 logos_d5005_shell #(.NB(NB), .AW(AW), .DWM(DWM), .ECC(ECC), .MBW(MBW), .HAW(HAW), .DWH(DWH), .HBW(HBW), .MAW(MAW),
5034 .AFU_ID_L(AFU_ID_L), .AFU_ID_H(AFU_ID_H), .BUILD(BUILD)) shell(
5035 .pclk(pclk), .pclk_rst_n(rst_n), .uclk(uclk), .uclk_rst_n(rst_n),{uclk2_cosim}
5036 .mmio_address(mmio_address), .mmio_read(mmio_read), .mmio_write(mmio_write), .mmio_writedata(mmio_writedata),
5037 .mmio_byteenable(8'hff), .mmio_waitrequest(mmio_waitrequest), .mmio_readdata(mmio_readdata),
5038 .mmio_readdatavalid(mmio_readdatavalid),
5039 .host_rd_address(host_rd_address), .host_rd_read(host_rd_read), .host_rd_burstcount(host_rd_burstcount),
5040 .host_rd_waitrequest(host_rd_waitrequest), .host_rd_readdata(host_rd_readdata), .host_rd_readdatavalid(host_rd_readdatavalid),
5041 .host_wr_address(host_wr_address), .host_wr_write(host_wr_write), .host_wr_burstcount(host_wr_burstcount),
5042 .host_wr_writedata(host_wr_writedata), .host_wr_byteenable(host_wr_byteenable), .host_wr_waitrequest(host_wr_waitrequest),
5043 .host_wr_writeresponsevalid(host_wr_writeresponsevalid),
5044 .mem_clk(mem_clk), .mem_rst_n({NB{rst_n}}), .mem_address(mem_address), .mem_read(mem_read), .mem_write(mem_write),
5045 .mem_burstcount(mem_burstcount), .mem_writedata(mem_writedata), .mem_byteenable(mem_byteenable),
5046 .mem_waitrequest(mem_waitrequest), .mem_readdata(mem_readdata), .mem_readdatavalid(mem_readdatavalid));
5047
5048 // Each channel's lines and the host's, a toggle flipped in its own clock on each one that moves: a run whose program
5049 // waits on a command while no line of memory or of the host moves for STALL compute cycles has stalled, far past any
5050 // job's own quiet stretch, and ends saying so rather than waiting out the program's polls.
5051 parameter STALL = 1000000;
5052 wire [NB-1:0] moved;
5053 genvar b;
5054 generate for (b = 0; b < NB; b = b + 1) begin : channel
5055 {channel_model} memory(.clk(mem_clk[b]),
5056 .rst_n(rst_n), .address(mem_address[b*AW +: AW]), .read(mem_read[b]), .write(mem_write[b]),
5057 .burstcount(mem_burstcount[b*MBW +: MBW]), .writedata(mem_writedata[b*DWM +: DWM]),
5058 .byteenable(mem_byteenable[b*DWM/8 +: DWM/8]), .waitrequest(mem_waitrequest[b]), .readdata(mem_readdata[b*DWM +: DWM]),
5059 .readdatavalid(mem_readdatavalid[b]));
5060 reg flip = 1'b0;
5061 always @(posedge mem_clk[b]) if (mem_readdatavalid[b] || ((mem_read[b] || mem_write[b]) && !mem_waitrequest[b])) flip <= ~flip;
5062 assign moved[b] = flip;
5063 end endgenerate
5064 reg host_flip = 1'b0;
5065 always @(posedge pclk)
5066 if (host_rd_readdatavalid || (host_rd_read && !host_rd_waitrequest) || (host_wr_write && !host_wr_waitrequest)) host_flip <= ~host_flip;
5067 reg [NB:0] seen = 0;
5068 longint quiet = 0;
5069 always @(posedge uclk) if (rst_n) begin
5070 if ({host_flip, moved} != seen) begin
5071 seen <= {host_flip, moved};
5072 quiet <= 0;
5073 end else if (quiet == STALL * DIVIDE) begin
5074 $display("logos_cosim: stalled: no line of memory or of the host moved for %0d compute cycles", STALL);
5075 $finish;
5076 end else quiet <= quiet + 1;
5077 end
5078
5079 logos_host_memory_vpi #(.AW(HAW), .DW(DWH), .BW(HBW)) host(.clk(pclk), .rst_n(rst_n),
5080 .rd_address(host_rd_address), .rd_read(host_rd_read), .rd_burstcount(host_rd_burstcount), .rd_waitrequest(host_rd_waitrequest),
5081 .rd_readdata(host_rd_readdata), .rd_readdatavalid(host_rd_readdatavalid), .wr_address(host_wr_address), .wr_write(host_wr_write),
5082 .wr_burstcount(host_wr_burstcount), .wr_writedata(host_wr_writedata), .wr_waitrequest(host_wr_waitrequest),
5083 .wr_writeresponsevalid(host_wr_writeresponsevalid));
5084
5085`ifdef LOGOS_DPI
5086 import "DPI-C" function void logos_dpi_program(input longint core_period_ps);
5087 import "DPI-C" function int logos_dpi_mmio(input bit valid, input longint data, output int address, output bit read,
5088 output bit write, output longint wdata);
5089 import "DPI-C" function int logos_dpi_exit_status();
5090 int dpi_address;
5091 bit dpi_read, dpi_write;
5092 longint dpi_wdata;
5093 initial begin
5094 #50000 rst_n = 1;
5095 logos_dpi_program(UCLK_PS * DIVIDE);
5096 end
5097 always @(negedge pclk) if (rst_n) begin
5098 if (logos_dpi_mmio(mmio_readdatavalid, mmio_readdata, dpi_address, dpi_read, dpi_write, dpi_wdata) != 0) begin
5099 $display("logos_cosim: the program exited %0d", logos_dpi_exit_status());
5100 $finish;
5101 end
5102 mmio_address = dpi_address[MAW-1:0];
5103 mmio_read = dpi_read;
5104 mmio_write = dpi_write;
5105 mmio_writedata = dpi_wdata;
5106 end
5107`else
5108 initial begin
5109 #50000 rst_n = 1;
5110 $logos_program(UCLK_PS * DIVIDE);
5111 end
5112 always @(negedge pclk) if (rst_n) $logos_mmio(mmio_readdatavalid, mmio_readdata, mmio_address, mmio_read, mmio_write, mmio_writedata);
5113`endif
5114endmodule
5115
5116// Host memory behind the PIM's Avalon-MM read/write port, its lines the host program's buffers: waitrequest at random
5117// on each channel, read data in order after a random latency, one write response a burst.
5118module logos_host_memory_vpi #(parameter AW = 51, parameter DW = 512, parameter BW = 7, parameter SEED = 11,
5119 parameter WAIT_PCT = 25) (
5120 input clk,
5121 input rst_n,
5122 input [AW-1:0] rd_address,
5123 input rd_read,
5124 input [BW-1:0] rd_burstcount,
5125 output reg rd_waitrequest,
5126 output reg [DW-1:0] rd_readdata,
5127 output reg rd_readdatavalid,
5128 input [AW-1:0] wr_address,
5129 input wr_write,
5130 input [BW-1:0] wr_burstcount,
5131 input [DW-1:0] wr_writedata,
5132 output reg wr_waitrequest,
5133 output reg wr_writeresponsevalid
5134);
5135`ifdef LOGOS_DPI
5136 import "DPI-C" function int logos_dpi_host_read(input longint line, output bit [511:0] data);
5137 import "DPI-C" function int logos_dpi_host_write(input longint line, input bit [511:0] data);
5138`endif
5139 integer seed = SEED, cycle = 0, qh = 0, qt = 0, beat = 0, rh = 0, rt = 0;
5140 reg [AW-1:0] q_addr [0:255];
5141 reg [BW-1:0] q_len [0:255];
5142 integer q_due [0:255];
5143 integer r_due [0:255];
5144 reg [AW-1:0] w_addr;
5145 reg [BW-1:0] w_left = 0;
5146 reg [63:0] line;
5147 reg [DW-1:0] word;
5148 always @(posedge clk) begin
5149 cycle = cycle + 1;
5150 rd_readdatavalid <= 1'b0;
5151 wr_writeresponsevalid <= 1'b0;
5152 if (rst_n) begin
5153 if (rd_read && !rd_waitrequest) begin
5154 q_addr[qt % 256] = rd_address; q_len[qt % 256] = rd_burstcount;
5155 q_due[qt % 256] = cycle + 20 + (($random(seed) % 40 + 40) % 40); qt = qt + 1;
5156 end
5157 if (wr_write && !wr_waitrequest) begin
5158 if (w_left == 0) begin
5159 line = wr_address; w_addr = wr_address + 1; w_left = wr_burstcount - 1;
5160 end else begin
5161 line = w_addr; w_addr = w_addr + 1; w_left = w_left - 1;
5162 end
5163 word = wr_writedata;
5164`ifdef LOGOS_DPI
5165 if (logos_dpi_host_write(line, word) != 0) begin
5166 $display("logos_cosim: host write of I/O address 0x%0h, which no buffer holds", line << 6);
5167 $finish;
5168 end
5169`else
5170 $logos_host_write(line, word);
5171`endif
5172 if (w_left == 0) begin r_due[rt % 256] = cycle + 5 + (($random(seed) % 30 + 30) % 30); rt = rt + 1; end
5173 end
5174 if (qh != qt && q_due[qh % 256] <= cycle) begin
5175 line = q_addr[qh % 256] + beat;
5176`ifdef LOGOS_DPI
5177 if (logos_dpi_host_read(line, word) != 0) begin
5178 $display("logos_cosim: host read of I/O address 0x%0h, which no buffer holds", line << 6);
5179 $finish;
5180 end
5181`else
5182 $logos_host_read(line, word);
5183`endif
5184 rd_readdata <= word;
5185 rd_readdatavalid <= 1'b1;
5186 beat = beat + 1;
5187 if (beat == q_len[qh % 256]) begin beat = 0; qh = qh + 1; end
5188 end
5189 if (rh != rt && r_due[rh % 256] <= cycle) begin wr_writeresponsevalid <= 1'b1; rh = rh + 1; end
5190 rd_waitrequest <= (($random(seed) % 100 + 100) % 100) < WAIT_PCT;
5191 wr_waitrequest <= (($random(seed) % 100 + 100) % 100) < WAIT_PCT;
5192 end else begin
5193 rd_waitrequest <= 1'b1;
5194 wr_waitrequest <= 1'b1;
5195 end
5196 end
5197endmodule
5198"##;
5199
5200const MEMORY_MODEL: &str = r##"// A memory behind an Avalon-MM slave port, for simulation: waitrequest at random, a read burst's lines taken as the
5201// memory holds them when the burst is accepted and returned in order after a random latency with random gaps, so a
5202// write accepted after a read never reaches it, bursts on reads and writes, lines addressed by index.
5203module logos_avmm_memory_model #(parameter AW = 27, parameter DW = 64, parameter BW = 7, parameter LINES_LOG2 = 12,
5204 parameter SEED = 7, parameter WAIT_PCT = 30, parameter GAP_PCT = 20) (
5205 input clk,
5206 input rst_n,
5207 input [AW-1:0] address,
5208 input read,
5209 input write,
5210 input [BW-1:0] burstcount,
5211 input [DW-1:0] writedata,
5212 input [DW/8-1:0] byteenable,
5213 output reg waitrequest,
5214 output reg [DW-1:0] readdata,
5215 output reg readdatavalid
5216);
5217 reg [DW-1:0] mem [0:(1<<LINES_LOG2)-1];
5218 integer seed = SEED;
5219 // Accepted read bursts, in order: {beats, ready time}; and their lines, in order.
5220 reg [BW-1:0] q_len [0:255];
5221 integer q_due [0:255];
5222 reg [DW-1:0] q_data [0:4095];
5223 integer qh = 0, qt = 0, lh = 0, lt = 0, cycle = 0, beat = 0;
5224 reg [AW-1:0] w_addr;
5225 reg [BW-1:0] w_left = 0;
5226 integer i;
5227 initial for (i = 0; i < (1 << LINES_LOG2); i = i + 1) mem[i] = {DW{1'bx}};
5228 always @(posedge clk) begin
5229 cycle = cycle + 1;
5230 readdatavalid <= 1'b0;
5231 if (rst_n) begin
5232 if (read && !waitrequest) begin
5233 q_len[qt % 256] = burstcount;
5234 q_due[qt % 256] = cycle + 10 + (($random(seed) % 30 + 30) % 30);
5235 qt = qt + 1;
5236 for (i = 0; i < burstcount; i = i + 1) begin
5237 q_data[lt % 4096] = mem[(address + i) % (1 << LINES_LOG2)];
5238 lt = lt + 1;
5239 end
5240 end
5241 if (write && !waitrequest) begin
5242 if (w_left == 0) begin
5243 mem[address[LINES_LOG2-1:0]] = writedata;
5244 w_addr = address + 1;
5245 w_left = burstcount - 1;
5246 end else begin
5247 mem[w_addr[LINES_LOG2-1:0]] = writedata;
5248 w_addr = w_addr + 1;
5249 w_left = w_left - 1;
5250 end
5251 end
5252 if (qh != qt && q_due[qh % 256] <= cycle && (($random(seed) % 100 + 100) % 100) >= GAP_PCT) begin
5253 readdata <= q_data[lh % 4096];
5254 readdatavalid <= 1'b1;
5255 lh = lh + 1;
5256 beat = beat + 1;
5257 if (beat == q_len[qh % 256]) begin
5258 beat = 0;
5259 qh = qh + 1;
5260 end
5261 end
5262 waitrequest <= (($random(seed) % 100 + 100) % 100) < WAIT_PCT;
5263 end else begin
5264 waitrequest <= 1'b1;
5265 end
5266 end
5267endmodule
5268"##;