Skip to content

Commit 0ccf882

Browse files
committed
Fix tests
1 parent f78893b commit 0ccf882

File tree

7 files changed

+232
-10
lines changed

7 files changed

+232
-10
lines changed

.github/workflows/ci.yml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,24 @@ jobs:
1313

1414
steps:
1515
- uses: actions/checkout@v3
16+
- name: Set up Python
17+
uses: actions/setup-python@v5
18+
with:
19+
python-version: '3.x'
1620
- name: install dependencies
17-
run: sudo apt install nasm
21+
run: |
22+
sudo apt install nasm
23+
python -m pip install --upgrade pip
24+
pip install -r requirements.txt
25+
- name: Install iverilog
26+
run: |
27+
git clone https://github.com/steveicarus/iverilog.git
28+
cd iverilog
29+
./autogen.sh
30+
./configure
31+
make
32+
make install
33+
cd ..
1834
- name: make test
1935
run: make test
2036
- name: make

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
.vscode/
22
__pycache__/
33
build/
4+
output/

planner/__main__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from planner.asm import program_parser
66
from planner.sim import bin_parser
7-
from planner.sim import io
7+
from planner.sim import devices
88

99

1010
def args_parser():
@@ -38,8 +38,8 @@ def main():
3838
if args.source == "bin":
3939
with open(args.bin_file, "r") as f:
4040
_bin = bin_parser.BinRunner(f.read())
41-
_bin.set_input_device(5, io.Numpad("id(5), range(0-9)"))
42-
_bin.set_output_device(6, io.IntegerOutput("Screen6"))
41+
_bin.set_input_device(5, devices.Numpad("id(5), range(0-9)"))
42+
_bin.set_output_device(6, devices.IntegerOutput("Screen6"))
4343
while True:
4444
_bin.step()
4545

planner/asm_parser.py

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
import logging
2+
from typing import List, Union, Optional
3+
4+
from planner import instruction, unit, util
5+
6+
from planner.asm import line_parser
7+
8+
class LabelsManager:
9+
def __init__(self):
10+
self.labels = {}
11+
self.passive_labels = []
12+
13+
def add_lazy(self, lazy: unit.LazyLabel):
14+
if lazy.name == util.LABEL_CONSTANT:
15+
# do nothing
16+
return
17+
self.passive_labels.append(lazy)
18+
19+
def propogate(self):
20+
for label in self.passive_labels:
21+
if label.name not in self.labels:
22+
raise ValueError(f"'{label.name}' couldn't be resolved")
23+
label.assign(self.labels[label.name])
24+
25+
def new_label(self, name: str, value: int):
26+
label = unit.LazyLabel(name, value)
27+
assert name not in self.labels, f"repeated label found: {name}"
28+
# this is already resolved label
29+
assert isinstance(value, int)
30+
self.labels[name] = label
31+
return label
32+
33+
34+
class AsmParser:
35+
def __init__(self) -> None:
36+
self.reset()
37+
38+
def reset(self):
39+
self.address = 0
40+
self.lm = LabelsManager()
41+
self.section = "text"
42+
# self.ins = [] # type: List[instruction.ParsedInstruction]
43+
self.final_bytes = [] # type: Union[instruction.ParsedInstruction, unit.Data]
44+
45+
def get_str(self, resolved=False, rom_binary=False):
46+
'''Get printable instructions.
47+
48+
Also removes don't care byte(s) at the end coming
49+
from .bss section (if rom_binary is true).
50+
'''
51+
if rom_binary:
52+
resolved = True # implict
53+
if resolved:
54+
self.lm.propogate()
55+
track_binary_address = None
56+
_content = []
57+
for add, x in self.final_bytes:
58+
if not rom_binary:
59+
_content.append(f"{add:03x}: {x.get_str(resolved=resolved, binary=False)}")
60+
else:
61+
if track_binary_address is None:
62+
track_binary_address = add
63+
assert track_binary_address == add, "gaps found in binary representation"
64+
out = f"{x.get_str(resolved=resolved, binary=True)}"
65+
_content.append(out)
66+
assert len(out) % 8 == 0
67+
track_binary_address += len(out)//8
68+
69+
content = '\n'.join(_content)
70+
if rom_binary:
71+
_program_content = (content
72+
.replace("\n", "")
73+
.replace(" ", "")
74+
.replace("-", " ")
75+
.rstrip()
76+
)
77+
assert len(_program_content) % 8 == 0
78+
_program_size = len(_program_content) // 8 # bytes
79+
assert _program_size < 2**8 # as we are using 1 byte for metadata
80+
_binary_content = f"{_program_size:08b}" + _program_content
81+
assert len(_binary_content) % 8 == 0
82+
assert set(_binary_content) <= set(['0', '1']), "only binary context is expected"
83+
84+
content = '\n'.join([
85+
"%s %s %s %s" % (
86+
_binary_content[32*i:32*i+8],
87+
_binary_content[32*i+8:32*i+16],
88+
_binary_content[32*i+16:32*i+24],
89+
_binary_content[32*i+24:32*i+32])
90+
for i in range((len(_binary_content)+24)//32)
91+
])
92+
return content
93+
94+
def get_section(self):
95+
return self.section
96+
97+
def section_update(self, name):
98+
assert name in ["text", "data", "bss"]
99+
if self.get_section() == "bss" and name != "bss":
100+
raise ValueError(".bss must be the last section")
101+
self.section = name
102+
103+
def get_address(self):
104+
return self.address
105+
106+
def add_address(self, add):
107+
self.address += add
108+
109+
def add_ins(self, ins: instruction.ParsedInstruction):
110+
# self.ins.append(ins)
111+
self.final_bytes.append((self.get_address(), ins))
112+
self.add_address(ins.size())
113+
114+
def add_data(self, data: unit.Data):
115+
self.final_bytes.append((self.get_address(), data))
116+
self.add_address(data.size())
117+
118+
def parse_text(self, tokens: List[str], line: str):
119+
if tokens[0].endswith(":"):
120+
# label
121+
assert len(tokens) == 1, "label: should exists in isolation in line"
122+
assert len(tokens[0]) >= 2, "label should be atleast 1 char long"
123+
label = tokens[0][:-1]
124+
self.lm.new_label(label, self.get_address())
125+
return
126+
ins_name, tokens = line_parser.parse_line(line)
127+
if ins_name is None:
128+
# no instruction
129+
return
130+
for _, token in tokens:
131+
self.lm.add_lazy(token)
132+
self.add_ins(instruction.get_parser(ins_name).parse(tokens))
133+
134+
def parse_data(self, tokens: List[str]):
135+
label = tokens[0]
136+
self.lm.new_label(label, self.get_address())
137+
138+
times = 1
139+
if tokens[1] == "times":
140+
times = int(tokens[2])
141+
tokens = [tokens[0]] + tokens[3:]
142+
143+
assert len(tokens) == 3, "Three tokens expected in .data other than times"
144+
if tokens[1] == "db":
145+
sz = 1
146+
elif tokens[1] == "dw":
147+
sz = 2
148+
else:
149+
raise ValueError("unsupported data size provided")
150+
151+
# unsigned integer only for now
152+
val = int(tokens[2])
153+
assert val >= 0 and val < (2**(8*sz))
154+
155+
for _ in range(times):
156+
for byte in val.to_bytes(sz, 'big'):
157+
self.add_data(unit.Data(byte))
158+
159+
def parse_bss(self, tokens: List[str]):
160+
if len(tokens) == 3:
161+
assert tokens[0].endswith(":"), f"bss tokens: {tokens}"
162+
label = tokens[0][:-1]
163+
self.lm.new_label(label, self.get_address())
164+
tokens = tokens[1:]
165+
166+
assert tokens[0] == "resb"
167+
sz = int(tokens[1])
168+
for _ in range(sz):
169+
self.add_data(unit.Data(None))
170+
171+
def parse_constant(self, tokens: List[str]):
172+
assert tokens[1].lower() == "equ"
173+
self.lm.new_label(tokens[0], int(tokens[2], 0))
174+
return
175+
176+
def parse_line(self, line: str):
177+
line = line.strip()
178+
if len(line) == 0:
179+
# empty line
180+
return
181+
if line.startswith("#"):
182+
# comment
183+
return
184+
# case insensitive
185+
tokens = [x for x in line.split()]
186+
187+
if tokens[0].lower() == "section":
188+
assert len(tokens) == 2,f"expects section <what>, line: {line}"
189+
assert tokens[1].lower() in [".text", ".data", ".bss"]
190+
self.section_update(tokens[1][1:].lower())
191+
return
192+
193+
if len(tokens) == 3 and tokens[1].lower() == "equ":
194+
# constants
195+
self.parse_constant(tokens)
196+
return
197+
198+
if self.get_section() == "text":
199+
self.parse_text(tokens, line)
200+
elif self.get_section() == "data":
201+
self.parse_data(tokens)
202+
elif self.get_section() == "bss":
203+
self.parse_bss(tokens)
204+
else:
205+
raise Exception(f"unknown section: {self.get_section()}")

planner/sim/bin_parser.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from typing import List, Optional
55
from planner import instruction
6-
from planner.sim import io
6+
from planner.sim import devices
77

88
PROGRAM_ORG = 0x40
99
IO_DEVICES = 16
@@ -23,10 +23,10 @@ def __init__(self, content):
2323
self.is_powered_on = True
2424
# self.step()
2525

26-
def set_input_device(self, index: int, d: io.InputDevice):
26+
def set_input_device(self, index: int, d: devices.InputDevice):
2727
self.input_devices[index] = d
2828

29-
def set_output_device(self, index: int, d: io.Device):
29+
def set_output_device(self, index: int, d: devices.Device):
3030
self.output_devices[index] = d
3131

3232
def parse(self, content: str):

planner/sim/bin_parser_test.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from planner.asm import program_parser
22
from planner.sim import bin_parser
3-
from planner.sim import io
3+
from planner.sim import devices
44
from unittest import TestCase
55

66

@@ -29,8 +29,8 @@ def test_overall(self):
2929
binary_program = asm.get_str(resolved=True, rom_binary=True)
3030

3131
_bin = bin_parser.BinRunner(binary_program)
32-
fake_input = io.LatchInput("fake")
33-
fake_ouput = io.Device()
32+
fake_input = devices.LatchInput("fake")
33+
fake_ouput = devices.Device()
3434

3535
_bin.set_input_device(5, fake_input)
3636
_bin.set_output_device(6, fake_ouput)

0 commit comments

Comments
 (0)