Get 100% code coverage
[app-scheme79asm.git] / lib / App / Scheme79asm.pm
CommitLineData
509643aa
MG
1package App::Scheme79asm;
2
3use 5.014000;
4use strict;
5use warnings;
1756f229
MG
6use re '/s';
7use Carp qw/croak/;
509643aa 8
4a9c3fa9 9use Data::Dumper qw/Dumper/;
509643aa
MG
10use Data::SExpression qw/consp scalarp/;
11use Scalar::Util qw/looks_like_number/;
12
6c9fb71e 13our $VERSION = '0.005001';
509643aa
MG
14
15our %TYPES = (
16 LIST => 0,
17 SYMBOL => 1,
4a9c3fa9 18 NUMBER => 1,
509643aa
MG
19 VAR => 2,
20 VARIABLE => 2,
21 CLOSURE => 3,
22 PROC => 4,
23 PROCEDURE => 4,
4a9c3fa9 24 IF => 5,
509643aa
MG
25 COND => 5,
26 CONDITIONAL => 5,
27 CALL => 6,
509643aa
MG
28 QUOTE => 7,
29 QUOTED => 7,
4a9c3fa9
MG
30
31 MORE => 0,
32 CAR => 1,
33 CDR => 2,
34 CONS => 3,
35 ATOM => 4,
36 PROGN => 5,
f0289d3d 37 'REVERSE-LIST' => 6,
4a9c3fa9 38 FUNCALL => 7,
509643aa
MG
39);
40
41*consp = *Data::SExpression::consp;
42*scalarp = *Data::SExpression::scalarp;
43
44sub process {
4a9c3fa9
MG
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);
34073a4a 62 my ($comment_type, $comment_addr) = ($type, $addr);
4a9c3fa9 63 die 'Computed addr is not a number: ', Dumper($addr), "\n" unless looks_like_number $addr;
509643aa 64
ab8f838f 65 if (!looks_like_number $type) {
509643aa
MG
66 die "No such type: $type\n" unless exists $TYPES{$type};
67 $type = $TYPES{$type};
509643aa
MG
68 }
69
f0289d3d 70 $addr += (1 << $self->{addr_bits}) if $addr < 0;
1756f229
MG
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});
509643aa 73 my $result = ($type << $self->{addr_bits}) + $addr;
2aba73d5 74
4a9c3fa9
MG
75 unless ($location) {
76 $self->{freeptr}++;
77 $location = $self->{freeptr}
78 }
79 $self->{memory}[$location] = $result;
34073a4a 80 $self->{comment}[$location] = "$comment_type $comment_addr";
4a9c3fa9 81 $location
509643aa
MG
82}
83
84sub parse {
85 my ($self, $string) = @_;
4a9c3fa9 86 my $ds = Data::SExpression->new({symbol_case => 'up', use_symbol_class => 1, fold_lists => 1});
509643aa
MG
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}];
34073a4a 99 $self->{comment}[5] = $self->{comment}[$self->{freeptr}];
509643aa
MG
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];
5ff3fe85
MG
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 }
509643aa
MG
114 bless \%args, $class
115}
116
f0289d3d
MG
117sub print_binary16 {
118 my ($self, $fh) = @_;
744f5c2a 119 $fh //= \*STDOUT; # uncoverable condition right
f0289d3d 120
26bb1344
MG
121 die "addr_bits + type_bits >= 16\n"if $self->{addr_bits} + $self->{type_bits} > 16;
122
f0289d3d 123 my $length = @{$self->{memory}};
04be023f 124 print $fh pack 'n', $length or croak "Failed to print memory size: $!"; # uncoverable branch true
f0289d3d 125 for (@{$self->{memory}}) {
04be023f 126 print $fh pack 'n', $_ or croak "Failed to print memory: $!" # uncoverable branch true
f0289d3d
MG
127 }
128}
129
130sub print_verilog {
509643aa 131 my ($self, $fh) = @_;
744f5c2a 132 $fh //= \*STDOUT; # uncoverable condition right
509643aa
MG
133
134 my $bits = $self->{type_bits} + $self->{addr_bits};
34073a4a
MG
135 my $index_length = length $#{$self->{memory}};
136 my $index_format = '%' . $index_length . 'd';
509643aa
MG
137 for my $index (0 .. $#{$self->{memory}}) {
138 my $val = $self->{memory}[$index];
34073a4a 139 my $comment = $self->{comment}[$index];
509643aa
MG
140 if ($index == 4) {
141 $val = "${bits}'d$val"
142 } else {
143 $val = $val ? sprintf "%d'b%0${bits}b", $bits, $val : '0';
144 }
34073a4a
MG
145 my $spaces = ' ' x ($bits + 5 - (length $val));
146 $index = sprintf $index_format, $index;
744f5c2a 147
1756f229
MG
148 my $string = "mem[$index] <= $val;";
149 $string .= "$spaces // $comment" if defined $comment;
04be023f 150 say $fh $string or croak "Failed to print verilog: $!"; # uncoverable branch true
509643aa 151 }
f0289d3d
MG
152
153}
154sub parse_and_print_binary16 {
155 my ($self, $string, $fh) = @_;
156 $self->parse($string);
157 $self->finish;
158 $self->print_binary16($fh);
509643aa
MG
159}
160
f0289d3d 161sub parse_and_print_verilog {
509643aa
MG
162 my ($self, $string, $fh) = @_;
163 $self->parse($string);
164 $self->finish;
f0289d3d 165 $self->print_verilog($fh);
509643aa
MG
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);
f0289d3d 181 $asm->parse_and_print_verilog('(number 70)');
509643aa
MG
182
183=head1 DESCRIPTION
184
509643aa
MG
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
9924aa7b
MG
190S-expressions. Each S-expression is a list of one of three types:
191
744f5c2a 192C<(tag value)>, for example C<(symbol 2)>, represents a value to be
9924aa7b 193put in memory (for example a number, or a symbol, or a variable
5ff3fe85 194reference). The value must be a number.
9924aa7b
MG
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
68367057
MG
214The C<tag> is either a number, a type, or a primitive.
215The available types are:
509643aa 216
68367057
MG
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
f0289d3d 253=item REVERSE-LIST
68367057
MG
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.
5ff3fe85 277Therefore the word size is C<type_bits + address_bits> (default 11).
68367057
MG
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
5ff3fe85
MG
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.
68367057 298
68367057
MG
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
8ff4c670
MG
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
68367057
MG
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
f0289d3d
MG
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>])
68367057
MG
330
331Print a block of Verilog code assigning the memory contents to an
332array named C<mem> to the given filehandle (default STDOUT).
333
f0289d3d
MG
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>])
68367057
MG
340
341Convenience method that calls B<parse>($string), B<finish>, and then
f0289d3d 342B<print_verilog>($fh).
68367057
MG
343
344=back
509643aa
MG
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.033872 seconds and 4 git commands to generate.