Skip to content

Commit 9abfd08

Browse files
committed
Add CPU generator
1 parent 465e882 commit 9abfd08

File tree

10 files changed

+487
-159
lines changed

10 files changed

+487
-159
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ debug.json
1111
/src/main.mlog
1212
/src/peripherals/debugger.mlog
1313
/src/config/*.mlog
14+
/src/cpu/*.mlog
1415
/src/peripherals/scrolling_display.mlog
1516
*.labels.mlog
1617

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ ASM_PROGRAMS = $(patsubst asm/%.s,%,$(wildcard asm/*.s))
22

33
RUST_PROGRAMS = $(filter-out webserver,$(patsubst rust/examples/%,%,$(wildcard rust/examples/*)))
44

5-
MLOG_PROGRAMS = $(patsubst src/%.mlog.jinja,%,$(filter-out src/config/base.mlog.jinja,$(wildcard src/*.mlog.jinja) $(wildcard src/*/*.mlog.jinja)))
5+
MLOG_PROGRAMS = $(patsubst src/%.mlog.jinja,%,$(wildcard src/*.mlog.jinja) $(wildcard src/peripherals/*.mlog.jinja))
66

77
.PHONY: all
88
all: asm rust

python/src/mlogv32/preprocessor/app.py

Lines changed: 269 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
1+
from __future__ import annotations
2+
13
import re
24
from pathlib import Path
3-
from typing import Annotated, Any, TypedDict
5+
from typing import Annotated, Any, TypedDict, Unpack, cast
46

57
import yaml
68
from jinja2 import Environment, FileSystemLoader, StrictUndefined
9+
from pymsch import Block, Content, ProcessorConfig, ProcessorLink, Schematic
710
from typer import Option, Typer
811

12+
from mlogv32.utils.msch import BEContent
13+
914
from .extensions import CommentStatement
1015
from .filters import FILTERS
11-
from .parser.mlog import Label, Statement, parse_mlog
16+
from .models import BuildConfig
17+
from .parser import iter_labels, parse_mlog
1218

1319
app = Typer(
1420
pretty_exceptions_show_locals=False,
@@ -20,17 +26,9 @@ def file_command(
2026
path: Path,
2127
output: Annotated[Path | None, Option("-o", "--output")] = None,
2228
):
23-
path = path.resolve()
24-
25-
env = create_jinja_env(path.parent)
26-
template = env.get_template(path.name)
27-
result = template.render()
29+
"""Preprocess a single .mlog.jinja file."""
2830

29-
if output:
30-
output.parent.mkdir(parents=True, exist_ok=True)
31-
output.write_text(result, "utf-8")
32-
else:
33-
print(result)
31+
render_template(path.resolve(), output)
3432

3533

3634
@app.command()
@@ -39,6 +37,8 @@ def labels(
3937
output: Annotated[Path | None, Option("-o", "--output")] = None,
4038
filter_str: Annotated[str | None, Option("--filter")] = None,
4139
):
40+
"""Extract label addresses from a mlog file into set statements."""
41+
4242
if filter_str:
4343
filter_re = re.compile(filter_str)
4444
else:
@@ -47,16 +47,11 @@ def labels(
4747
with path.open("r", encoding="utf-8") as f:
4848
lines = parse_mlog(f.read())
4949

50-
result_lines = list[str]()
51-
line_num = 0
52-
for line in lines:
53-
match line:
54-
case Label(name=name):
55-
if not filter_re or filter_re.fullmatch(name):
56-
result_lines.append(f"set {name} {line_num}")
57-
case Statement():
58-
line_num += 1
59-
result = "\n".join(result_lines)
50+
result = "\n".join(
51+
f"set {name} {line_num}"
52+
for name, line_num in iter_labels(lines)
53+
if not filter_re or filter_re.fullmatch(name)
54+
)
6055

6156
if output:
6257
output.write_text(result, encoding="utf-8")
@@ -66,6 +61,8 @@ def labels(
6661

6762
@app.command()
6863
def configs(yaml_path: Path):
64+
"""Generate CPU configs."""
65+
6966
with yaml_path.open("rb") as f:
7067
data: ConfigsYaml = yaml.load(f, yaml.Loader)
7168

@@ -78,6 +75,236 @@ def configs(yaml_path: Path):
7875
(output_dir / name).with_suffix(".mlog").write_text(result, "utf-8")
7976

8077

78+
@app.command()
79+
def build(
80+
yaml_path: Path,
81+
width: Annotated[int, Option("-w", "--width")] = 16,
82+
height: Annotated[int, Option("-h", "--height")] = 16,
83+
size: Annotated[int | None, Option("-s", "--size")] = None,
84+
output: Annotated[Path | None, Option("-o", "--output")] = None,
85+
cpu_only: bool = False,
86+
):
87+
"""Generate a CPU schematic."""
88+
89+
if size:
90+
width = size
91+
height = size
92+
93+
config = BuildConfig.load(yaml_path)
94+
95+
worker_output = get_template_output_path(config.templates.worker)
96+
worker_code = render_template(config.templates.worker, worker_output)
97+
98+
labels = dict(iter_labels(parse_mlog(worker_code)))
99+
100+
controller_output = get_template_output_path(config.templates.controller)
101+
controller_code = render_template(
102+
config.templates.controller,
103+
controller_output,
104+
instructions=config.instructions,
105+
labels=labels,
106+
)
107+
108+
lookups_schem = cast(
109+
Schematic,
110+
Schematic.read_file(str(config.schematics.lookups)), # type: ignore
111+
)
112+
assert lookups_schem.get_dimensions() == (4, 4)
113+
114+
ram_schem = cast(
115+
Schematic,
116+
Schematic.read_file(str(config.schematics.ram)), # type: ignore
117+
)
118+
assert ram_schem.get_dimensions() == (1, 1)
119+
120+
schem = Schematic()
121+
122+
for x in lenrange(0, 16):
123+
for y in lenrange(0, 16):
124+
schem.add_block(simple_block(BEContent.TILE_LOGIC_DISPLAY, x, y))
125+
126+
display_link = ProcessorLink(0, 0, "display1")
127+
128+
schem.add_schem(lookups_schem, 0, 16)
129+
lookup_links = [
130+
ProcessorLink(x=i % 4, y=16 + i // 4, name=f"processor{i + 1}")
131+
for i in range(16)
132+
]
133+
134+
add_label(schem, 4, 19, right="UART0, UART2", down="LABELS")
135+
schem.add_block(simple_block(Content.WORLD_CELL, 4, 18))
136+
schem.add_block(simple_block(Content.WORLD_CELL, 4, 17))
137+
add_label(schem, 4, 16, up="COSTS", right="UART1, UART3")
138+
139+
labels_link = ProcessorLink(4, 18, "cell1")
140+
costs_link = ProcessorLink(4, 17, "cell2")
141+
142+
uart_links = list[ProcessorLink]()
143+
for x in lenrange(5, 4, 2):
144+
for y in lenrange(18, -4, -2):
145+
schem.add_block(simple_block(Content.MEMORY_BANK, x, y))
146+
uart_links.append(ProcessorLink(x, y, f"bank{len(uart_links) + 1}"))
147+
148+
schem.add_block(simple_block(Content.MEMORY_CELL, 9, 19))
149+
schem.add_schem(ram_schem, 9, 18)
150+
schem.add_schem(ram_schem, 9, 17)
151+
schem.add_block(Block(Content.MICRO_PROCESSOR, 9, 16, ProcessorConfig("", []), 0))
152+
153+
registers_link = ProcessorLink(9, 19, "cell3")
154+
csrs_link = ProcessorLink(9, 18, "processor17")
155+
incr_link = ProcessorLink(9, 17, "processor18")
156+
config_link = ProcessorLink(9, 16, "processor19")
157+
158+
add_with_label(
159+
schem,
160+
simple_block(Content.MESSAGE, 11, 19),
161+
left="REGISTERS",
162+
right="ERROR_OUTPUT",
163+
)
164+
add_with_label(
165+
schem,
166+
Block(Content.SWITCH, 11, 18, False, 0),
167+
left="CSRS",
168+
right="POWER_SWITCH",
169+
)
170+
add_with_label(
171+
schem,
172+
Block(Content.SWITCH, 11, 17, False, 0),
173+
left="INCR",
174+
right="PAUSE_SWITCH",
175+
)
176+
add_with_label(
177+
schem,
178+
Block(Content.SWITCH, 11, 16, False, 0),
179+
left="CONFIG",
180+
right="SINGLE_STEP_SWITCH",
181+
)
182+
183+
error_output_link = ProcessorLink(11, 19, "message1")
184+
power_switch_link = ProcessorLink(11, 18, "switch2")
185+
pause_switch_link = ProcessorLink(11, 17, "switch3")
186+
single_step_switch_link = ProcessorLink(11, 16, "switch1")
187+
188+
# hack
189+
if cpu_only:
190+
schem = Schematic()
191+
192+
schem.add_block(
193+
Block(
194+
block=Content.WORLD_PROCESSOR,
195+
x=16,
196+
y=0,
197+
config=ProcessorConfig(
198+
code=controller_code,
199+
links=relative_links(
200+
*lookup_links,
201+
*uart_links,
202+
labels_link,
203+
costs_link,
204+
csrs_link,
205+
incr_link,
206+
config_link,
207+
error_output_link,
208+
single_step_switch_link,
209+
power_switch_link,
210+
pause_switch_link,
211+
display_link,
212+
x=16,
213+
y=0,
214+
),
215+
),
216+
rotation=0,
217+
)
218+
)
219+
220+
controller_link = ProcessorLink(16, 0, "processor20")
221+
222+
for x in lenrange(16, width):
223+
for y in lenrange(0, height):
224+
# controller
225+
if x == 16 and y == 0:
226+
continue
227+
228+
schem.add_block(
229+
Block(
230+
block=Content.WORLD_PROCESSOR,
231+
x=x,
232+
y=y,
233+
config=ProcessorConfig(
234+
code=worker_code,
235+
links=relative_links(
236+
*lookup_links,
237+
*uart_links,
238+
labels_link,
239+
costs_link,
240+
registers_link,
241+
csrs_link,
242+
incr_link,
243+
config_link,
244+
controller_link,
245+
error_output_link,
246+
single_step_switch_link,
247+
display_link,
248+
x=x,
249+
y=y,
250+
),
251+
),
252+
rotation=0,
253+
)
254+
)
255+
256+
if output:
257+
schem.write_file(str(output))
258+
else:
259+
schem.write_clipboard()
260+
261+
262+
def lenrange(start: int, length: int, step: int = 1):
263+
return range(start, start + length, step)
264+
265+
266+
def simple_block(block: Content | BEContent, x: int, y: int):
267+
return Block(block, x, y, None, 0)
268+
269+
270+
def relative_links(*links: ProcessorLink, x: int, y: int):
271+
return [ProcessorLink(link.x - x, link.y - y, link.name) for link in links]
272+
273+
274+
class Labels(TypedDict, total=False):
275+
up: str
276+
left: str
277+
right: str
278+
down: str
279+
280+
281+
def add_label(schem: Schematic, x: int, y: int, **labels: Unpack[Labels]):
282+
label = "\n".join(
283+
template.format(label)
284+
for key, template in [
285+
("up", "{} ⇧"),
286+
("left", "⇦ {}"),
287+
("right", "{} ⇨"),
288+
("down", "{} ⇩"),
289+
]
290+
if (label := labels.get(key))
291+
)
292+
schem.add_block(
293+
Block(
294+
block=Content.MESSAGE,
295+
x=x,
296+
y=y,
297+
config=label,
298+
rotation=0,
299+
)
300+
)
301+
302+
303+
def add_with_label(schem: Schematic, block: Block, **labels: Unpack[Labels]):
304+
add_label(schem, block.x - 1, block.y, **labels)
305+
schem.add_block(block)
306+
307+
81308
def create_jinja_env(template_dir: Path):
82309
env = Environment(
83310
loader=FileSystemLoader(template_dir),
@@ -95,6 +322,26 @@ def create_jinja_env(template_dir: Path):
95322
return env
96323

97324

325+
def render_template(path: Path, output: Path | None, **kwargs: Any):
326+
env = create_jinja_env(path.parent)
327+
template = env.get_template(path.name)
328+
result = template.render(**kwargs)
329+
330+
if output:
331+
output.parent.mkdir(parents=True, exist_ok=True)
332+
output.write_text(result, "utf-8")
333+
else:
334+
print(result)
335+
336+
return result
337+
338+
339+
def get_template_output_path(path: Path):
340+
if path.suffix != ".jinja":
341+
raise ValueError(f"Expected .jinja suffix, but got {path.suffix}: {path}")
342+
return path.with_suffix("")
343+
344+
98345
class ConfigsYaml(TypedDict):
99346
template: str
100347
defaults: dict[str, Any]

0 commit comments

Comments
 (0)