-
Notifications
You must be signed in to change notification settings - Fork 13
/
gen_alu
executable file
·242 lines (205 loc) · 6.89 KB
/
gen_alu
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
use Storable;
# Script to generate the contents of the ALU ROM.
# (C) 2019 Warren Toomey, GPL3
#
# The ROM takes these 21 bits of input.
# - bit 20-16, the ALU operation
# - bit 15-8, the A value
# - bit 7-0, the B value
#
# There are 13 bits of output.
# - bit 12, the divide-by-zero (D) bit, active high
# - bit 11, the negative (N) bit, active high
# - bit 10, the zero (Z) bit, active high
# - bit 9, the overflow (V) bit, active high
# - bit 8, the carry (C) bit, active high
# - bit 7-0, the result
#
# All other output bits are unused and not wired up.
use constant DIVFLAG => 0x1000; # The divide-by-zero flag
use constant NEGFLAG => 0x0800; # The negative flag
use constant ZEROFLAG => 0x0400; # The zero flag
use constant OFLOWFLAG => 0x0200; # The overflow flag
use constant CARRYFLAG => 0x0100; # The carry flag
use constant CMASK => 0x01ff; # Mask to keep carry bit from Perl operation
# Global variables to make writing the anonymous subs easier
our ($A, $B, $result); # A and B inputs, and ALU result
# The ROM contents to be generated
my @ROM;
# Lookup tables to convert BCD to 8-bit numbers and vice versa
my @BCDtoNum;
my @NumtoBCD;
# Function to build the above two lookup tables
sub init_bcd_tables {
foreach my $i (0x00 .. 0xff) {
# Firstly convert what might be two BCD digits to an 8-bit value.
# Any BCD digit above 9 is treated as 0.
my $topdigit= $i >> 4;
my $botdigit= $i & 0xf;
$topdigit=0 if ($topdigit > 9);
$botdigit=0 if ($botdigit > 9);
# Convert to an 8-bit value and store in the table
$BCDtoNum[$i]= $topdigit * 10 + $botdigit;
# Now do the opposite, take an 8-bit value and convert to two
# BCD digits. Any 8-bit value above 99 is changed to 0.
my $decimalnum= ($i>99) ? 0 : $i;
$topdigit= int($decimalnum/10);
$botdigit= $decimalnum%10;
# Convert to two BCD nibbles and store in the table
$NumtoBCD[$i]= ($topdigit<<4) + $botdigit;
}
}
# Some operations are complicated enough that they are in their own subroutine.
# Rotate $A to the left $B times. Slow but works.
sub ROL {
$result= $A;
for (my $i=0; $i < $B; $i++) {
my $msb= $result & 0x80;
$result= ($result << 1) & 0xff;
$result |= ($msb) ? 1 : 0;
}
}
# Rotate $A to the right $B times. Slow but works.
sub ROR {
$result= $A;
for (my $i=0; $i < $B; $i++) {
my $lsb= $result & 0x1;
$result= $result >> 1;
$result |= ($lsb) ? 0x80 : 0;
}
}
# Arithmetic shift $A to the right $B times. Slow but works.
sub ASR {
$result= $A;
my $msb= $result & 0x80;
for (my $i=0; $i < $B; $i++) {
$result= $result >> 1;
$result |= $msb;
}
}
# Two digit BCD addition of $A and $B.
# Any result >100 is reduced by 100 and carry set.
# Result is also two BCD digits.
sub BCD_ADD {
# Convert both to 8-bit values
my $a= $BCDtoNum[ $A ];
my $b= $BCDtoNum[ $B ];
my $c=0;
$result= $a + $b;
if ($result > 99) {
$result -= 99;
$c= CARRYFLAG; # This is active high at this point
}
$result= $NumtoBCD[ $result] + $c;
}
# Two digit BCD addition of $A and $B.
# Any result <0 is incremented by 100 and carry set.
# Result is also two BCD digits.
sub BCD_SUB {
# Convert both to 8-bit values
my $a= $BCDtoNum[ $A ];
my $b= $BCDtoNum[ $B ];
my $c=0;
$result= $a - $b;
if ($result < 0) {
$result += 99;
$c= CARRYFLAG; # This is active high at this point
}
$result= $NumtoBCD[ $result] + $c;
}
# Array of subroutines, some anonymous, which calculate the result
# given the A and B values
my @Opsub= (
sub { $result= 0 }, # 0
sub { $result= $A; }, # A
sub { $result= $B; }, # B
sub { $result= (-$A) & CMASK; }, # -A
sub { $result= (-$B) & CMASK; }, # -B
sub { $result= ($A+1) & CMASK; }, # A+1
sub { $result= ($B+1) & CMASK; }, # B+1
sub { $result= ($A-1) & CMASK; }, # A-1
sub { $result= ($B-1) & CMASK; }, # B-1
sub { $result= ($A+$B) & CMASK; }, # A+B
sub { $result= ($A+$B+1) & CMASK; }, # A+B+1
sub { $result= ($A-$B) & CMASK; }, # A-B
sub { $result= ($A-$B) & CMASK; }, # A-B special, op 12
sub { $result= ($B-$A) & CMASK; }, # B-A
sub { $result= ($A-$B-1) & CMASK; }, # A-B-1
sub { $result= ($B-$A-1) & CMASK; }, # B-A-1
sub { $result= ($A*$B) & 0xff; }, # A*B, low bits
sub { $result= ($A*$B) >> 8; }, # A*B, high bits
sub { $result= ($B==0) ? 0 : int($A/$B); }, # A/B
sub { $result= ($B==0) ? 0 : int($A%$B); }, # A%B
sub { $result= ($A<<$B) & CMASK; }, # A<<B
sub { $result= $A>>$B; }, # A>>B logical
\&ASR, # A>>B arithmetic
\&ROL, # A ROL B
\&ROR, # A ROR B,
sub { $result= $A&$B; }, # A AND B
sub { $result= $A|$B; }, # A OR B
sub { $result= $A^$B; }, # A XOR B
sub { $result= (~$A) & 0xff; }, # NOT A
sub { $result= (~$B) & 0xff; }, # NOT B
\&BCD_ADD, # BCD A + B
\&BCD_SUB, # BCD A + B
);
### MAIN PROGRAM ###
# Generate the BCD tables
init_bcd_tables();
# Loop across all possible ALU inputs
foreach my $aluop (0x00 .. 0x1f) {
# Cache the ALU op subroutine and if it's a div or mod
my $opsub= $Opsub[$aluop];
my $isdivmod = ($aluop==18 || $aluop==19) ? 1 : 0;
foreach $A (0x00 .. 0xff) {
# Cache A's sign
my $asign= $A & 0x80;
foreach $B (0x00 .. 0xff) {
my $bsign= $B & 0x80;
# Run the subroutine to calculate the result
$opsub->();
my $rsign= $result & 0x80;
# At this point we have an active high carry flag in bit 8
# and an active high negative bit in bit 7
# Add on any zero flag
$result |= ZEROFLAG if (($result&0xff)==0);
# Flip the zero bit for special ALUop 12
$result ^= ZEROFLAG if ($aluop==12);
# Add on any active low negative flag
$result |= NEGFLAG if ($rsign);
# If A's sign is the same as B's sign, and the
# result sign is different, set the overflow flag
$result |= OFLOWFLAG if (($asign==$bsign) && ($asign != $rsign));
# Put in the divide-by-zero flag if B is zero and we did a DIV or MOD
$result |= DIVFLAG if ($isdivmod && $B==0);
# Put the result into the ROM
$ROM[ ($aluop<<16) | ($A<<8) | $B ] = $result;
}
}
}
# Write ROM out in hex for Verilog
open( my $OUT, ">", "alu.rom" ) || die("Can't write to alu.rom: $!\n");
for my $i ( 0 .. ( 2**21 - 1 ) ) {
printf( $OUT "%x ", $ROM[$i] ? $ROM[$i] : 0 );
print( $OUT "\n" ) if ( ( $i % 8 ) == 7 );
}
close($OUT);
# If there is a ROMs directory, also write out eight minipro binary ROM file,
# each of which has little-endian 16-bit values.
if (-d "ROMs") {
my $offset= 0;
my $lastposn= 2**18 - 1;
foreach my $bank (0 .. 7) {
open($OUT, '>:raw', "ROMs/alu$bank.rom") or die "Unable to open: $!";
for my $i ( $offset .. $lastposn ) {
print($OUT pack("v", $ROM[$i] ? $ROM[$i] : 0 ));
}
close($OUT);
$offset += 2**18; $lastposn += 2**18;
}
}
exit(0);