Compare commits

...

6 Commits

Author SHA1 Message Date
b6e9d5ae86 Makefile: add static build 2023-08-23 20:04:30 -07:00
dd205b5e3e stmt_string: add missing strings 2023-08-23 19:55:18 -07:00
c12374f5fd lexer: add support for line continuations 2023-08-23 19:47:55 -07:00
c2c59495b3 lexer: add support for binary number literals 2023-08-23 19:34:09 -07:00
118942521e test: add initial test makefile
This also adds support for "#" characters prior to immediates.

nop may also now appear in an op_t. The parser no longer generates nop_t--this
is instead now represented as an op_t with a zero-length ops vector.
2023-08-23 19:21:43 -07:00
f51ec95713 grammar: update
This moves the expression grammar from parser.cpp to grammar.txt.
2023-08-23 19:17:10 -07:00
16 changed files with 200 additions and 62 deletions

4
.gitignore vendored
View File

@ -1,4 +1,6 @@
main
scu-dsp-asm
*.o
*.gch
*.d
*.d
test/*.s

View File

@ -1,8 +1,9 @@
CXXFLAGS = -Og -g -Wall -Wextra -Werror -Wfatal-errors -Wpedantic -Wno-c99-designator -std=c++20
STATIC = -static -static-libgcc -static-libstdc++
CXXFLAGS = -Og -g -Wall -Wextra -Werror -Wfatal-errors -Wno-c99-designator -std=c++20
LDFLAGS =
TARGET =
CXX = $(TARGET)clang++
CXX = $(TARGET)g++
SRC = main.cpp
SRC += lexer.cpp
@ -14,15 +15,15 @@ SRC += stmt_string.cpp
OBJ = $(patsubst %.cpp,%.o,$(SRC))
DEP = $(patsubst %.cpp,%.d,$(SRC))
all: main
all: scu-dsp-asm
-include $(DEP)
%.o: %.cpp
$(CXX) $(CXXFLAGS) -MMD -MF $(basename $<).d -c $< -o $@
main: $(OBJ)
$(CXX) $(LDFLAGS) $^ -o $@
scu-dsp-asm: $(OBJ)
$(CXX) $(STATIC) $(LDFLAGS) $^ -o $@
clean:
rm -f *.o *.d *.gch main

View File

@ -131,7 +131,7 @@ uint32_t emitter_t::visit(const op::mov_ram_a_t * mov_ram_a) const
uint32_t emitter_t::visit(const op::mov_imm_d1_t * mov_imm_d1) const
{
num_t value = mov_imm_d1->imm.expr->accept(this);
num_t value = mov_imm_d1->imm.normalize(mov_imm_d1->imm.expr->accept(this));
if (mov_imm_d1->imm.in_range(value))
return mov_imm_d1->code() | mov_imm_d1->bits() | value;
else
@ -152,7 +152,7 @@ uint32_t emitter_t::visit(const op::control_word_t * control_word) const
uint32_t emitter_t::visit(const load::mvi_t * mvi) const
{
num_t value = mvi->imm.expr->accept(this);
num_t value = mvi->imm.normalize(mvi->imm.expr->accept(this));
if (mvi->imm.in_range(value))
return mvi->code() | mvi->bits() | value;
else
@ -161,7 +161,7 @@ uint32_t emitter_t::visit(const load::mvi_t * mvi) const
uint32_t emitter_t::visit(const load::mvi_cond_t * mvi_cond) const
{
num_t value = mvi_cond->imm.expr->accept(this);
num_t value = mvi_cond->imm.normalize(mvi_cond->imm.expr->accept(this));
if (mvi_cond->imm.in_range(value))
return mvi_cond->code() | mvi_cond->bits() | value;
else
@ -170,7 +170,7 @@ uint32_t emitter_t::visit(const load::mvi_cond_t * mvi_cond) const
uint32_t emitter_t::visit(const dma::src_d0_imm_t * src_d0_imm) const
{
num_t value = src_d0_imm->imm.expr->accept(this);
num_t value = src_d0_imm->imm.normalize(src_d0_imm->imm.expr->accept(this));
if (src_d0_imm->imm.in_range(value))
return src_d0_imm->code() | src_d0_imm->bits() | value;
else
@ -179,7 +179,7 @@ uint32_t emitter_t::visit(const dma::src_d0_imm_t * src_d0_imm) const
uint32_t emitter_t::visit(const dma::d0_dst_imm_t * d0_dst_imm) const
{
num_t value = d0_dst_imm->imm.expr->accept(this);
num_t value = d0_dst_imm->imm.normalize(d0_dst_imm->imm.expr->accept(this));
if (d0_dst_imm->imm.in_range(value))
return d0_dst_imm->code() | d0_dst_imm->bits() | value;
else
@ -198,7 +198,7 @@ uint32_t emitter_t::visit(const dma::d0_dst_ram_t * d0_dst_ram) const
uint32_t emitter_t::visit(const jump::jmp_t * jmp) const
{
num_t value = jmp->imm.expr->accept(this);
num_t value = jmp->imm.normalize(jmp->imm.expr->accept(this));
if (jmp->imm.in_range(value))
return jmp->code() | jmp->bits() | value;
else
@ -207,7 +207,7 @@ uint32_t emitter_t::visit(const jump::jmp_t * jmp) const
uint32_t emitter_t::visit(const jump::jmp_cond_t * jmp_cond) const
{
num_t value = jmp_cond->imm.expr->accept(this);
num_t value = jmp_cond->imm.normalize(jmp_cond->imm.expr->accept(this));
if (jmp_cond->imm.in_range(value))
return jmp_cond->code() | jmp_cond->bits() | value;
else

View File

@ -22,11 +22,10 @@ struct emitter_error_t : std::runtime_error
struct emitter_t : visitor_t<uint32_t>
{
emitter_t(variables_t& variables, const addresses_t& addresses)
: variables(variables), addresses(addresses) {}
emitter_t(variables_t& variables)
: variables(variables) {}
variables_t& variables;
const addresses_t& addresses;
uint32_t visit(const binary_t * binary) const;
uint32_t visit(const grouping_t * grouping) const;

View File

@ -174,10 +174,10 @@ void resolver_t::visit(const assign_t * assign) const
void resolver_t::visit(const label_t * label) const
{
if (addresses.contains(label->name.lexeme)) {
if (variables.contains(label->name.lexeme)) {
throw std::runtime_error("label redefinition is not allowed");
} else {
addresses.insert({label->name.lexeme, pc.value});
variables.insert({label->name.lexeme, pc.value});
}
}

View File

@ -25,11 +25,11 @@ struct pc_t
struct resolver_t : visitor_t<void>
{
resolver_t(pc_t& pc, addresses_t& addresses)
: pc(pc), addresses(addresses) {}
resolver_t(pc_t& pc, variables_t& variables)
: pc(pc), variables(variables) {}
pc_t& pc;
addresses_t& addresses;
variables_t& variables;
void visit(const binary_t * binary) const;
void visit(const grouping_t * grouping) const;

View File

@ -1,3 +1,25 @@
expression → term
term → factor ( ( "-" | "+" ) factor )*
factor → unary ( ( "/" | "*" | "%" ) unary )*
unary → ( "~" | "+" | "-" ) unary
| shift
shift → andl ( ( "<<" | ">>" ) andl )*
andl → orl ( "&" orl )*
orl → andl ( ( "|" | "^" ) andl )*
primary → number
| "(" expression ")"
number → "%" base2-number
| "$" base16-number
| "0b" base2-number
| "0x" base16-number
| base10-number
imm → ("#")? expression
uimm8 → imm
uimm19 → imm
uimm25 → imm
alu → and | or | xor | add | sub | ad2 | sr | rr | sl | rl | rl8
xy_src → "mc0" | "mc1" | "mc2" | "mc3"
@ -25,12 +47,14 @@ d1_src → "mc0" | "mc1" | "mc2" | "mc3"
| "m0" | "m1" | "m2" | "m3"
| "alh" | "all"
mov_imm_d1 → "mov" simm8 "," d1_dest
mov_imm_d1 → "mov" uimm8 "," d1_dest
mov_ram_d1 → "mov" d1_src "," d1_dest
d1_bus → mov_imm_d1 → mov_ram_d1
op → ( alu | x_bus | y_bus | d1_bus ) +
nop → "nop"
op → ( nop | alu | x_bus | y_bus | d1_bus ) +
load_dest → "mc0" | "mc1" | "mc2" | "mc3"
| "rx" | "pl"
@ -50,7 +74,7 @@ load → mvi | mvi_cond
dma_ingress → "m0" | "m1" | "m2" | "m3"
| "prg"
dma_egress → "m0" | "m1" | "m2" | "m3"
add_mode = "0" | "1" | "2" | "4" | "8" | "16" | "32" | "64"
@ -60,8 +84,8 @@ dma_dmah → ("dma" | "dmah") add_mode?
dma_length_ram → "m0" | "m1" | "m2" | "m3"
| "mc0" | "mc1" | "mc2" | "mc3"
dma_ingress_imm → dma_dmah "d0" "," dma_ingress "," simm8
dma_egress_imm → dma_dmah dma_egress "," "d0" "," simm8
dma_ingress_imm → dma_dmah "d0" "," dma_ingress "," uimm8
dma_egress_imm → dma_dmah dma_egress "," "d0" "," uimm8
dma_ingress_ram → dma_dmah "d0" "," dma_ingress "," dma_length_ram
dma_egress_ram → dma_dmah dma_egress "," "d0" "," dma_length_ram
@ -82,8 +106,7 @@ loop → "btm" | "lps"
end → "end" | "endi"
instruction → "nop"
| op
instruction → op
| load
| dma
| jump
@ -98,4 +121,3 @@ instruction_statement → label? instruction? "\n"
statement → assignment_statement
| instruction_statement

View File

@ -53,6 +53,19 @@ constexpr static N parse_number(const std::string_view s)
return n;
}
struct bin_t {
constexpr static bool pred(const char c)
{
return c >= '0' && c <= '1';
}
template <typename N>
constexpr static N parse(const std::string_view s)
{
return parse_number<N, 2>(s);
}
};
struct dec_t {
constexpr static bool pred(const char c)
{
@ -159,13 +172,13 @@ std::optional<token_t> lexer_t::lex_token()
case '-': return {{pos, minus, lexeme()}};
case '*': return {{pos, star, lexeme()}};
case '/': return {{pos, slash, lexeme()}};
case '%': return {{pos, percent, lexeme()}};
case '~': return {{pos, tilde, lexeme()}};
case '&': return {{pos, ampersand, lexeme()}};
case '|': return {{pos, bar, lexeme()}};
case '^': return {{pos, carot, lexeme()}};
case '=': return {{pos, equal, lexeme()}};
case ':': return {{pos, colon, lexeme()}};
case '#': return {{pos, hash, lexeme()}};
case '<':
if (match('<')) return {{pos, left_shift, lexeme()}};
break;
@ -175,8 +188,12 @@ std::optional<token_t> lexer_t::lex_token()
case ';':
while (!at_end_p() && peek() != '\n') advance();
break;
case ' ':
case '\\':
if (match('\n')) { continue; }
else if (match('\r')) { if (match('\n')) continue; }
break;
case '\r':
case ' ':
case '\t':
break;
case '\n':
@ -187,6 +204,13 @@ std::optional<token_t> lexer_t::lex_token()
return {{tmp, eol, lexeme()}};
}
break;
case '%':
if (bin_t::pred(peek())) {
start_ix += 1;
return {_number<bin_t>()};
} else {
return {{pos, percent, lexeme()}};
}
case '$':
if (hex_t::pred(peek())) {
start_ix += 1;
@ -198,7 +222,18 @@ std::optional<token_t> lexer_t::lex_token()
if (hex_t::pred(peek())) {
start_ix += 2;
return {_number<hex_t>()};
}
} else {
error(pos.line, pos.col, "expected hexadecimal literal");
return {};
}
} else if (match('b')) {
if (bin_t::pred(peek())) {
start_ix += 2;
return {_number<bin_t>()};
} else {
error(pos.line, pos.col, "expected binary literal");
return {};
}
}
[[fallthrough]];
default:

View File

@ -40,15 +40,14 @@ static void run(std::ostream& os, std::string source)
parser_t pass2(tokens);
ast::printer_t printer(std::cout);
ast::pc_t pc;
ast::addresses_t addresses;
ast::resolver_t resolver(pc, addresses);
ast::variables_t variables;
ast::resolver_t resolver(pc, variables);
while (auto stmt_o = pass1.statement()) {
(*stmt_o)->accept(&printer);
std::cout << std::endl << std::flush;
(*stmt_o)->accept(&resolver);
}
ast::variables_t variables;
ast::emitter_t emitter(variables, addresses);
ast::emitter_t emitter(variables);
while (auto stmt_o = pass2.statement()) {
uint32_t output = (*stmt_o)->accept(&emitter);
if (output != 0xffff'ffff) {

View File

@ -1,16 +1,3 @@
/*
expression term ;
term factor ( ( "-" | "+" ) factor )* ;
factor unary ( ( "/" | "*" | "%" ) unary )* ;
unary ( "~" | "+" | "-" ) unary
| shift ;
shift andl ( ( "<<" | ">>" ) andl )*
andl orl ( "&" orl )*
orl andl ( ( "|" | "^" ) andl )*
primary NUMBER
| "(" expression ")" ;
*/
#include <string>
#include <optional>
#include <cassert>
@ -44,6 +31,11 @@ const token_t& parser_t::peek()
return tokens[current_ix];
}
const token_t& parser_t::peek(int n)
{
return tokens[current_ix + n];
}
const token_t& parser_t::advance()
{
if (!at_end_p()) current_ix++;
@ -280,6 +272,7 @@ std::optional<op::op_t *> parser_t::xyd1_bus()
else
throw error(peek(), "expected x-bus, y-bus, or d-bus destination operand");
} else {
match(hash); // optionally consume a hash
uimm_t<8> imm = uimm_t<8>(peek(), expression());
consume(comma, "expected `,`");
if (auto dest_o = d1_dest())
@ -297,6 +290,7 @@ std::optional<op::op_t *> parser_t::xyd1_bus()
std::optional<stmt_t *> parser_t::op()
{
bool saw_nop = false;
std::vector<const op::op_t *> ops;
std::vector<const token_t *> tokens;
@ -315,11 +309,12 @@ std::optional<stmt_t *> parser_t::op()
while (true) {
// fixme: check for emplacement here
const token_t& token = peek();
if (auto op_o = alu() ) emplace_op(token, *op_o);
if (match(_nop)) saw_nop = 1;
else if (auto op_o = alu() ) emplace_op(token, *op_o);
else if (auto op_o = xyd1_bus()) emplace_op(token, *op_o);
else break;
}
if (ops.size() != 0)
if (ops.size() != 0 || saw_nop)
return {new op::control_word_t(ops)};
else
return {};
@ -367,6 +362,7 @@ std::optional<stmt_t *> parser_t::load()
{
if (match(_mvi)) {
const token_t& expr_token = peek();
match(hash); // optionally consume a hash
expr_t * expr = expression();
consume(comma, "expected `,`");
load::dest_t dest = parser_t::load_dest();
@ -512,6 +508,7 @@ std::optional<stmt_t *> parser_t::dma()
if (auto length_ram_o = dma_length_ram()) {
return {new dma::d0_dst_ram_t(hold, add, dst, *length_ram_o)};
} else {
match(hash); // optionally consume a hash
uimm_t<8> imm = uimm_t<8>(peek(), expression());
return {new dma::d0_dst_imm_t(hold, add, dst, imm)};
}
@ -523,6 +520,7 @@ std::optional<stmt_t *> parser_t::dma()
if (auto length_ram_o = dma_length_ram()) {
return {new dma::src_d0_ram_t(hold, add, src, *length_ram_o)};
} else {
match(hash); // optionally consume a hash
uimm_t<8> imm = uimm_t<8>(peek(), expression());
return {new dma::src_d0_imm_t(hold, add, src, imm)};
}
@ -553,9 +551,11 @@ std::optional<stmt_t *> parser_t::jump()
if (match(_jmp)) {
if (auto cond_o = jump_cond()) {
consume(comma, "expected `,` after jump condition");
match(hash); // optionally consume a hash
uimm_t<8> imm = uimm_t<8>(peek(), expression());
return {new jump::jmp_cond_t(*cond_o, imm)};
} else {
match(hash); // optionally consume a hash
uimm_t<8> imm = uimm_t<8>(peek(), expression());
return {new jump::jmp_t(imm)};
}
@ -579,8 +579,7 @@ std::optional<stmt_t *> parser_t::end()
std::optional<stmt_t *> parser_t::instruction()
{
if (match(_nop)) return {new nop::nop_t()};
else if (auto op_o = op()) return op_o;
if (auto op_o = op()) return op_o;
else if (auto load_o = load()) return load_o;
else if (auto dma_o = dma()) return dma_o;
else if (auto jump_o = jump()) return jump_o;

View File

@ -31,6 +31,7 @@ struct parser_t
const token_t& previous();
const token_t& peek();
const token_t& peek(int n);
const token_t& advance();
bool check(enum token_t::type_t token_type);
bool match(enum token_t::type_t token_type);

View File

@ -40,6 +40,15 @@ struct imm_t {
static constexpr num_t max = (1L << (bits - static_cast<num_t>(sign))) - 1;
static constexpr num_t min = sign ? -(max + 1) : 0;
num_t normalize(num_t value) const
{
if (!S && value > 2147483648) { // fixme: hack
return value & max;
} else {
return value;
}
}
bool in_range(num_t value) const
{
return value <= max && value >= min;
@ -257,7 +266,6 @@ struct control_word_t : stmt_accept_t<control_word_t>
control_word_t(std::vector<const op::op_t *> ops)
: ops(ops)
{
if (ops.size() == 0) throw std::runtime_error("zero-length ops");
}
const std::vector<const op_t *> ops;

View File

@ -22,17 +22,21 @@ const std::string alu_type_string[] = {
};
const std::string xy_src_string[] = {
[i(xy_src_t::mc0)] = "mc0",
[i(xy_src_t::mc1)] = "mc1",
[i(xy_src_t::mc2)] = "mc2",
[i(xy_src_t::mc3)] = "mc3",
[i(xy_src_t::m0 )] = "m0" ,
[i(xy_src_t::m1 )] = "m1" ,
[i(xy_src_t::m2 )] = "m2" ,
[i(xy_src_t::m3 )] = "m3" ,
[i(xy_src_t::mc0)] = "mc0",
[i(xy_src_t::mc1)] = "mc1",
[i(xy_src_t::mc2)] = "mc2",
[i(xy_src_t::mc3)] = "mc3",
};
const std::string d1_dest_string[] = {
[i(d1_dest_t::mc0)] = "mc0",
[i(d1_dest_t::mc1)] = "mc1",
[i(d1_dest_t::mc2)] = "mc2",
[i(d1_dest_t::mc3)] = "mc3",
[i(d1_dest_t::rx )] = "rx" ,
[i(d1_dest_t::pl )] = "pl" ,
[i(d1_dest_t::ra0)] = "ra0",
@ -46,14 +50,16 @@ const std::string d1_dest_string[] = {
};
const std::string d1_src_string[] = {
[i(d1_src_t::mc0)] = "mc0",
[i(d1_src_t::mc1)] = "mc1",
[i(d1_src_t::mc2)] = "mc2",
[i(d1_src_t::mc3)] = "mc3",
[i(d1_src_t::m0 )] = "m0" ,
[i(d1_src_t::m1 )] = "m1" ,
[i(d1_src_t::m2 )] = "m2" ,
[i(d1_src_t::m3 )] = "m3" ,
[i(d1_src_t::mc0)] = "mc0",
[i(d1_src_t::mc1)] = "mc1",
[i(d1_src_t::mc2)] = "mc2",
[i(d1_src_t::mc3)] = "mc3",
[i(d1_src_t::all)] = "all",
[i(d1_src_t::alh)] = "alh",
};
}

52
test/Makefile Normal file
View File

@ -0,0 +1,52 @@
DSPASM = dspasm.exe
SRC = sample1.asm sample2a.asm sample2b.asm sample3.asm
SRC += cmpnm.asm fbtrans.asm loop_pr.asm udiv.asm
EXPECT = $(patsubst %.asm,expect/%.bin,$(SRC))
ACTUAL = $(patsubst %.asm,actual/%.bin,$(SRC))
ALL = $(EXPECT) $(ACTUAL)
ALL_TXT = $(patsubst %.bin,%.txt,$(ALL))
all: $(ALL)
all-txt: $(ALL_TXT)
%.s: %.asm
@test -f $(DSPASM) || (echo $(DSPASM) does not exist--set the DSPASM make variable; exit 1)
@rm -f $@
echo '[autoexec]' > $@.conf
echo 'mount C $(dir $(DSPASM))' >> $@.conf
echo 'mount D $(dir $<)' >> $@.conf
echo 'D:' >> $@.conf
echo 'C:\$(notdir $(DSPASM)) $(notdir $<) $(notdir $@)' >> $@.conf
echo 'exit' >> $@.conf
dosbox -conf $@.conf
@rm $@.conf
mv $(shell echo '$@' | tr '[:lower:]' '[:upper:]') $@
%.txt: %.bin
python bin-dump.py $< > $@
%.txt: %.bin
python bin-dump.py $< > $@
expect/%.bin: %.s
@mkdir -p $(dir $@)
srec_cat -Output $@ -Binary $<
actual/%.bin: %.asm
@mkdir -p $(dir $@)
../main $< $@
clean:
rm -f expect/*.{bin,txt} actual/*.{bin,txt} *.s
.SUFFIXES:
.INTERMEDIATE:
.SECONDARY:
.PHONY: all clean
%: RCS/%,v
%: RCS/%
%: %,v
%: s.%
%: SCCS/s.%

12
test/bin-dump.py Normal file
View File

@ -0,0 +1,12 @@
import sys
import struct
with open(sys.argv[1], 'rb') as f:
b = f.read()
assert len(b) % 4 == 0, len(b)
for i in range(len(b) // 4):
word = b[i*4:i*4+4]
n, = struct.unpack('>I', word)
print(f'{n:>032b}')

View File

@ -25,6 +25,7 @@ struct token_t {
comma,
dot,
hash,
// operators
plus,
@ -81,6 +82,7 @@ struct token_t {
case comma : return os << "COMMA";
case dot : return os << "DOT";
case hash : return os << "HASH";
// operators
case plus : return os << "PLUS";