logicaffeine_compile/codegen_sva/
decision_workload.rs1use super::scalar_asm::compile_to_scalar_words;
7
8pub const DECIDE_NAME: &str = "decide";
10
11pub const DECIDE_FN: &str = "\
15## To decide (micro100: Int, fair100: Int, edge100: Int, base_size: Int, max_order: Int, inventory: Int, pos_cap: Int, book_ok: Int) -> Int:
16 Let mutable order_side be 0.
17 Let mutable order_size be 0.
18 If book_ok is equal to 1:
19 If micro100 is greater than fair100 + edge100:
20 Set order_side to 2.
21 Set order_size to base_size.
22 Otherwise:
23 If micro100 is less than fair100 - edge100:
24 Set order_side to 1.
25 Set order_size to base_size.
26 If order_size is greater than max_order:
27 Set order_size to max_order.
28 Let mutable signed_order be 0.
29 If order_side is equal to 1:
30 Set signed_order to order_size.
31 If order_side is equal to 2:
32 Set signed_order to 0 - order_size.
33 Let projected be inventory + signed_order.
34 Let mutable risk_ok be 1.
35 If projected is greater than pos_cap:
36 Set risk_ok to 0.
37 If projected is less than 0 - pos_cap:
38 Set risk_ok to 0.
39 If risk_ok is equal to 0:
40 Set signed_order to 0.
41 Return signed_order.
42";
43
44pub const LCG_A: i64 = 20077;
48pub const LCG_C: i64 = 12345;
49pub const LCG_MASK: i64 = 32767;
50
51pub const SOFT_CORE_REALBIST_ITERATIONS: u32 = 30000;
53
54pub fn inline_body(func: &str, args: &[&str], indent: usize) -> Result<(String, String), String> {
61 let mut lines = func.lines().filter(|l| !l.trim().is_empty());
62 let header = lines.next().ok_or("an empty function")?;
63 let params: Vec<&str> = header
64 .split_once('(')
65 .and_then(|(_, rest)| rest.split_once(')'))
66 .map(|(ps, _)| {
67 ps.split(',')
68 .map(|p| p.split(':').next().unwrap_or("").trim())
69 .filter(|p| !p.is_empty())
70 .collect()
71 })
72 .ok_or_else(|| format!("no parameter list in `{header}`"))?;
73 if params.len() != args.len() {
74 return Err(format!(
75 "{} arguments for {} parameters",
76 args.len(),
77 params.len()
78 ));
79 }
80 let body: Vec<&str> = lines.collect();
81 let (last, rest) = body.split_last().ok_or("an empty body")?;
82 if rest.iter().any(|l| l.trim_start().starts_with("Return ")) {
83 return Err("a Return before the body's last line has no inline equivalent".into());
84 }
85 let ret = last
86 .strip_prefix(" Return ")
87 .and_then(|e| e.strip_suffix('.'))
88 .ok_or_else(|| format!("the body must end in a top-level `Return e.`, not `{last}`"))?;
89 let literal = |a: &str| {
90 a.strip_prefix('-')
91 .unwrap_or(a)
92 .chars()
93 .all(|c| c.is_ascii_digit())
94 && !a.is_empty()
95 };
96 let substitute = |text: &str| -> String {
97 let mut t = text.to_string();
98 for (p, a) in params.iter().zip(args).filter(|(_, a)| literal(a)) {
99 t = replace_word(&t, p, a);
100 }
101 t
102 };
103 let pad = " ".repeat(indent);
104 let mut out = String::new();
105 for (p, a) in params.iter().zip(args) {
106 if p != a && !literal(a) {
107 out.push_str(&format!("{pad}Let {p} be {a}.\n"));
108 }
109 }
110 for l in rest {
111 let l = l
112 .strip_prefix(" ")
113 .ok_or_else(|| format!("body line `{l}` is not indented under the header"))?;
114 out.push_str(&format!("{pad}{}\n", substitute(l)));
115 }
116 Ok((out, substitute(ret)))
117}
118
119pub fn inline_call(
121 func: &str,
122 args: &[&str],
123 result: &str,
124 indent: usize,
125) -> Result<String, String> {
126 let (body, ret) = inline_body(func, args, indent)?;
127 Ok(format!(
128 "{body}{}Set {result} to {ret}.\n",
129 " ".repeat(indent)
130 ))
131}
132
133fn replace_word(text: &str, word: &str, with: &str) -> String {
135 let ident = |c: char| c.is_alphanumeric() || c == '_';
136 let mut out = String::new();
137 let mut rest = text;
138 while let Some(at) = rest.find(word) {
139 let before = rest[..at].chars().next_back();
140 let after = rest[at + word.len()..].chars().next();
141 out.push_str(&rest[..at]);
142 if before.map_or(true, |c| !ident(c)) && after.map_or(true, |c| !ident(c)) {
143 out.push_str(with);
144 } else {
145 out.push_str(word);
146 }
147 rest = &rest[at + word.len()..];
148 }
149 out.push_str(rest);
150 out
151}
152
153pub fn soft_core_realbist_source(iterations: u32) -> String {
158 let step = format!("Set s to (s * {LCG_A} + {LCG_C}) & {LCG_MASK}.");
159 let args = [
160 "micro100",
161 "10050",
162 "edge100",
163 "base_size",
164 "max_order",
165 "inventory",
166 "100",
167 "book_ok",
168 ];
169 let (decide, order) = inline_body(DECIDE_FN, &args, 8).expect("the HFT decision inlines");
170 format!(
171 "## Main\n\
172 \x20 Let mutable s be 1.\n\
173 \x20 Let mutable i be 0.\n\
174 \x20 While i is less than {iterations}:\n\
175 \x20 {step}\n\
176 \x20 Let micro100 be 10000 + (s shifted right by 8).\n\
177 \x20 Let edge100 be (s shifted right by 4) & 15.\n\
178 \x20 {step}\n\
179 \x20 Let base_size be 1 + (s shifted right by 9).\n\
180 \x20 Let max_order be 10 + ((s shifted right by 4) & 31).\n\
181 \x20 {step}\n\
182 \x20 Let inventory be (s shifted right by 7) - 128.\n\
183 \x20 Let book_ok be (s shifted right by 6) & 1.\n\
184 {decide}\
185 \x20 Show {order} * 4 + order_side.\n\
186 \x20 Set i to i + 1.\n"
187 )
188}
189
190pub fn soft_core_realbist_program() -> Vec<u32> {
193 compile_to_scalar_words(&soft_core_realbist_source(SOFT_CORE_REALBIST_ITERATIONS))
194 .unwrap_or_else(|e| panic!("the CPU twin's program leaves the scalar subset: {e}"))
195}
196
197pub fn soft_core_realbist_verilog() -> String {
200 "// soft_core_realbist — the routable scalar LOGOS core running the HFT decision over the\n\
201 // realbist's input ranges (codegen_sva::decision_workload): the CPU twin of decision_hft_realbist.\n\
202 // Its boot ROM (program.hex, the emitted soft_core_realbist.hex) draws the inputs from a 15-bit LCG,\n\
203 // decides inline and shows signed_order * 4 + order_side once per decision. Emitted by\n\
204 // `cargo run -p logicaffeine-compile --example emit_soft_core_realbist`.\n\
205 module soft_core_realbist(input clk, input rst, output [31:0] out, output out_valid, output halted);\n\
206 \x20 soft_core_scalar core(.clk(clk), .rst(rst), .out(out), .out_valid(out_valid), .halted(halted));\n\
207 endmodule\n"
208 .to_string()
209}