Get 100% code coverage
[app-scheme79asm.git] / lib / App / Scheme79asm.pm
... / ...
CommitLineData
1package App::Scheme79asm;
2
3use 5.014000;
4use strict;
5use warnings;
6use re '/s';
7use Carp qw/croak/;
8
9use Data::Dumper qw/Dumper/;
10use Data::SExpression qw/consp scalarp/;
11use Scalar::Util qw/looks_like_number/;
12
13our $VERSION = '0.005001';
14
15our %TYPES = (
16 LIST => 0,
17 SYMBOL => 1,
18 NUMBER => 1,
19 VAR => 2,
20 VARIABLE => 2,
21 CLOSURE => 3,
22 PROC => 4,
23 PROCEDURE => 4,
24 IF => 5,
25 COND => 5,
26 CONDITIONAL => 5,
27 CALL => 6,
28 QUOTE => 7,
29 QUOTED => 7,
30
31 MORE => 0,
32 CAR => 1,
33 CDR => 2,
34 CONS => 3,
35 ATOM => 4,
36 PROGN => 5,
37 'REVERSE-LIST' => 6,
38 FUNCALL => 7,
39);
40
41*consp = *Data::SExpression::consp;
42*scalarp = *Data::SExpression::scalarp;
43
44sub process {
45 my ($self, $sexp, $location) = @_;
46 die 'Toplevel is not a list: ', Dumper($sexp), "\n" unless ref $sexp eq 'ARRAY';
47 my ($type, @addrs) = @$sexp;
48 my $addr;
49
50 die 'Type of toplevel is not atom: '. Dumper($type), "\n" unless scalarp($type);
51
52 if (@addrs > 1) {
53 $addr = $self->{freeptr} + 1;
54 $self->{freeptr} += @addrs;
55 $self->process($addrs[$_], $addr + $_) for 0 .. $#addrs;
56 } else {
57 $addr = $addrs[0];
58 }
59
60 $addr = $self->process($addr) if ref $addr eq 'ARRAY';
61 die 'Addr of toplevel is not atom: ', Dumper($addr), "\n" unless scalarp($addr);
62 my ($comment_type, $comment_addr) = ($type, $addr);
63 die 'Computed addr is not a number: ', Dumper($addr), "\n" unless looks_like_number $addr;
64
65 if (!looks_like_number $type) {
66 die "No such type: $type\n" unless exists $TYPES{$type};
67 $type = $TYPES{$type};
68 }
69
70 $addr += (1 << $self->{addr_bits}) if $addr < 0;
71 die "Type too large: $type\n" if $type >= (1 << $self->{type_bits});
72 die "Addr too large: $addr\n" if $addr >= (1 << $self->{addr_bits});
73 my $result = ($type << $self->{addr_bits}) + $addr;
74
75 unless ($location) {
76 $self->{freeptr}++;
77 $location = $self->{freeptr}
78 }
79 $self->{memory}[$location] = $result;
80 $self->{comment}[$location] = "$comment_type $comment_addr";
81 $location
82}
83
84sub parse {
85 my ($self, $string) = @_;
86 my $ds = Data::SExpression->new({symbol_case => 'up', use_symbol_class => 1, fold_lists => 1});
87
88 my $sexp;
89 while () {
90 last if $string =~ /^\s*$/;
91 ($sexp, $string) = $ds->read($string);
92 $self->process($sexp)
93 }
94}
95
96sub finish {
97 my ($self) = @_;
98 $self->{memory}[5] = $self->{memory}[$self->{freeptr}];
99 $self->{comment}[5] = $self->{comment}[$self->{freeptr}];
100 $self->{memory}[4] = $self->{freeptr};
101 delete $self->{memory}[$self->{freeptr}]
102}
103
104sub new {
105 my ($class, %args) = @_;
106 $args{type_bits} //= 3;
107 $args{addr_bits} //= 8;
108 $args{freeptr} //= 6;
109 $args{memory} //= [0, 0, (1<<$args{addr_bits}), (1<<$args{addr_bits}), 0, 0, 0];
110 my @default_comments = ('(cdr part of NIL)', '(car part of NIL)', '(cdr part of T)', '(car part of T)', '(free storage pointer)', '', '(result of computation)');
111 for (0 .. $#default_comments) {
112 $args{comment}[$_] = $default_comments[$_]
113 }
114 bless \%args, $class
115}
116
117sub print_binary16 {
118 my ($self, $fh) = @_;
119 $fh //= \*STDOUT; # uncoverable condition right
120
121 die "addr_bits + type_bits >= 16\n"if $self->{addr_bits} + $self->{type_bits} > 16;
122
123 my $length = @{$self->{memory}};
124 print $fh pack 'n', $length or croak "Failed to print memory size: $!"; # uncoverable branch true
125 for (@{$self->{memory}}) {
126 print $fh pack 'n', $_ or croak "Failed to print memory: $!" # uncoverable branch true
127 }
128}
129
130sub print_verilog {
131 my ($self, $fh) = @_;
132 $fh //= \*STDOUT; # uncoverable condition right
133
134 my $bits = $self->{type_bits} + $self->{addr_bits};
135 my $index_length = length $#{$self->{memory}};
136 my $index_format = '%' . $index_length . 'd';
137 for my $index (0 .. $#{$self->{memory}}) {
138 my $val = $self->{memory}[$index];
139 my $comment = $self->{comment}[$index];
140 if ($index == 4) {
141 $val = "${bits}'d$val"
142 } else {
143 $val = $val ? sprintf "%d'b%0${bits}b", $bits, $val : '0';
144 }
145 my $spaces = ' ' x ($bits + 5 - (length $val));
146 $index = sprintf $index_format, $index;
147
148 my $string = "mem[$index] <= $val;";
149 $string .= "$spaces // $comment" if defined $comment;
150 say $fh $string or croak "Failed to print verilog: $!"; # uncoverable branch true
151 }
152
153}
154sub parse_and_print_binary16 {
155 my ($self, $string, $fh) = @_;
156 $self->parse($string);
157 $self->finish;
158 $self->print_binary16($fh);
159}
160
161sub parse_and_print_verilog {
162 my ($self, $string, $fh) = @_;
163 $self->parse($string);
164 $self->finish;
165 $self->print_verilog($fh);
166}
167
1681;
169__END__
170
171=encoding utf-8
172
173=head1 NAME
174
175App::Scheme79asm - assemble sexp to Verilog ROM for SIMPLE processor
176
177=head1 SYNOPSIS
178
179 use App::Scheme79asm;
180 my $asm = App::Scheme79asm->new(type_bits => 3, addr_bits => 5);
181 $asm->parse_and_print_verilog('(number 70)');
182
183=head1 DESCRIPTION
184
185SIMPLE is a LISP processor defined in the 1979
186B<Design of LISP-Based Processors> paper by Steele and Sussman.
187
188The SIMPLE processor expects input in a particular tagged-pointer
189format. This module takes a string containing a sequence of
190S-expressions. Each S-expression is a list of one of three types:
191
192C<(tag value)>, for example C<(symbol 2)>, represents a value to be
193put in memory (for example a number, or a symbol, or a variable
194reference). The value must be a number.
195
196C<(tag list)>, where C<list> is of one of these three types,
197represents a tagged pointer. In this case, C<list> is (recursively)
198laid out in memory as per these rules, and a pointer to that location
199(and tagged C<tag>) is put somewhere in memory.
200
201C<(tag list1 list2)>, where C<list1> and C<list2> are of one of these
202three types (not necessarily the same type). In this case, C<list1>
203and C<list2> are (recursively) laid out in memory such that C<list1>
204is at position X and C<list2> is at position X+1, and a pointer of
205type tag and value X is put somewhere in memory.
206
207After this process the very last pointer placed in memory is moved to
208the special location 5 (which is where SIMPLE expects to find the
209expression to be evaluated).
210
211In normal use a single S-expression will be supplied, representing an
212entire program.
213
214The C<tag> is either a number, a type, or a primitive.
215The available types are:
216
217=over
218
219=item LIST
220
221=item SYMBOL (syn. NUMBER)
222
223=item VAR (syn. VARIABLE)
224
225=item CLOSURE
226
227=item PROC (syn. PROCEDURE)
228
229=item IF (syn. COND, CONDITIONAL)
230
231=item CALL
232
233=item QUOTE (syn. QUOTED)
234
235=back
236
237The available primitives are:
238
239=over
240
241=item MORE
242
243=item CAR
244
245=item CDR
246
247=item CONS
248
249=item ATOM
250
251=item PROGN
252
253=item REVERSE-LIST
254
255=item FUNCALL
256
257=back
258
259The following methods are available:
260
261=over
262
263=item App::Scheme79asm->B<new>([key => value, key => value, ...])
264
265Create a new assembler object. Takes a list of keys and values, here
266are the possible keys:
267
268=over
269
270=item type_bits
271
272=item address_bits
273
274A word is made of a type and an address, with the type occupying the
275most significant C<type_bits> (default 3) bits, and the address
276occupying the least significant C<address_bits> (default 8) bits.
277Therefore the word size is C<type_bits + address_bits> (default 11).
278
279=item freeptr
280
281A pointer to the last used byte in memory (default 6). The program
282will be laid out starting with location C<freeptr + 1>.
283
284=item memory
285
286The initial contents of the memory. Note that locations 4, 5, 6 will
287be overwritten, as will every location larger than the value of
288C<freeptr>.
289
290=item comment
291
292The initial comments for memory entries. C<< $comment->[$i] >> is the
293comment for C<< $memory->[$i] >>. Note that the first 7 entries of
294this array will be overwritten with the default comments. This is
295useful when using custom initial memory contents and freeptr, because
296this key can be used to provide comments for the extra reserved
297locations in memory.
298
299=back
300
301=item $asm->B<parse>(I<$string>)
302
303Parse a sequence of S-expressions and lay it out in memory.
304Can be called multiple times to lay out multiple sequences of
305S-expressions one after another.
306
307=item $asm->B<process>(I<$sexp>)
308
309Given an already-parsed sexp (meaning a
310L<Data::SExpression> object), lay it out in memory.
311Can be called multiple times to lay out multiple sequences of
312S-expressions one after another.
313
314=item $asm->B<finish>
315
316Move the last pointer to position 5, and put the free pointer at
317position 4. After all sequences of S-expressions have been given to
318B<parse>, this method should be called.
319
320=item $asm->B<print_binary16>([I<$fh>])
321
322Print the length of the memory (as a big-endian 16-bit value),
323followed by the memory contents as a sequence of big-endian 16-bit
324values to the given filehandle (default STDOUT). Dies if
325C<addr_bits + type_bits> is more than 16.
326
327Big-endian 16-bit values can be decoded with C<unpack 'n', $value>.
328
329=item $asm->B<print_verilog>([I<$fh>])
330
331Print a block of Verilog code assigning the memory contents to an
332array named C<mem> to the given filehandle (default STDOUT).
333
334=item $asm->B<parse_and_print_binary16>(I<$string>[, I<$fh>])
335
336Convenience method that calls B<parse>($string), B<finish>, and then
337B<print_binary16>($fh).
338
339=item $asm->B<parse_and_print_verilog>(I<$string>[, I<$fh>])
340
341Convenience method that calls B<parse>($string), B<finish>, and then
342B<print_verilog>($fh).
343
344=back
345
346=head1 SEE ALSO
347
348L<http://repository.readscheme.org/ftp/papers/ai-lab-pubs/AIM-514.pdf>
349
350=head1 AUTHOR
351
352Marius Gavrilescu, E<lt>marius@ieval.roE<gt>
353
354=head1 COPYRIGHT AND LICENSE
355
356Copyright (C) 2018 by Marius Gavrilescu
357
358This library is free software; you can redistribute it and/or modify
359it under the same terms as Perl itself, either Perl version 5.24.3 or,
360at your option, any later version of Perl 5 you may have available.
361
362
363=cut
This page took 0.01143 seconds and 4 git commands to generate.