############################################################################### # # File: Reg_macro.pm # Description: Module for parsing register macros from LaTeX documentation # Author: Matthew Lovell # Created: Mon Mar 31 16:50:05 2003 # Modified: Sat May 19 09:33:20 2018 # Language: CPerl # # (C) Copyright 2003, Matthew Lovell, all rights reserved. # ############################################################################### =pod =head1 NAME Reg_macro.pm =head1 SYNOPSIS Parses register/CSR information from LaTeX files. =head1 DESCRIPTION After searching through a files input/include tree, this module will search for all \begin{register} macros. All available information will be parsed out of each encountered macro. Parsed information includes: register name function offset field names, positions, and widths register reset value register reset mask register write mask The parsed information can either be used either to produce in-order tables for inclusion in documentation or to provide CSR information to Phase 1 tools (metacheckers, CSR checkers, etc). =head1 PUBLIC ROUTINES get_tex_files() Recursively follow a file's input/include/import tree get_regs() Parse CSR information from a single specified file build_reg_hash() Build a multi-level hash, keyed by function and offset, from the specified register information =head1 SEE ALSO Any project-specific LaTeX introductory material. =head1 AUTHOR Matthew Lovell (lovells@gmail.com) =cut ############################################################################### package Reg_macro; use Exporter; @ISA = qw(Exporter); @EXPORT = qw(get_tex_files get_regs build_reg_hash $REG_DEBUG $REG_STRIDE $REG_ERRORS ); use FileHandle; use Data::Dumper; # Debug flag $REG_DEBUG = 0; # How large are registers? Default is 64-bits. $REG_STRIDE = hex(0x08); # Error count $REG_ERRORS = 0; ############################################################################### # P U B L I C R O U T I N E S # ############################################################################### # Read a .tex file line by line, finding all \include or \input # commands. Recurse upon encountering one of those, thus making # a list of the entire TeX file tree. # # This subroutine can also be called with an option indicating that # all \includegraphics targets should be parsed. With that option, # this routine can extract all dependencies for a LaTeX file, which # is useful for GNU make. # # Arguments: filename # flag indicating all dependencies should be tracked # sub get_tex_files { my ($filename, $all_files) = @_; # return immediately for some file types if ($filename =~ /\.(eps|pdf|ps|func|error)$/) { print "#INFO Reg_macro Not attempting to read '$filename'\n" if ($REG_DEBUG); return $filename; } my $path = dirname($filename); # Add .tex if not present if ($filename !~ /\.\w+$/) { $filename .= '.tex'; } # initialize file list my @file_list = ($filename); my $file = new FileHandle "$filename"; unless (defined $file) { print STDERR "#ERROR Reg_macro Unable to open '$filename'\n"; $REG_ERRORS++; return @file_list; } # Allow files to turn off dependency extraction in particular sections, # when desired. This may be useful if a preamble needs to input a file # from the texmf tree, for example. my $dependency_skip = 0; while (<$file>) { $dependency_skip = 1 if (m/^%% DEPEND OFF/); $dependency_skip = 0 if (m/^%% DEPEND ON/); next if $dependency_skip; # Skip over comments. s/^%.*$//; # Remove whole comment lines s/([^\\])%.*$/$1/; # Remove everything after unescaped %'s # Get all include and input commands my @list = m/\\(?:include|input)\{([\w\/.-]+)\}/g; if ($path ne '' and $path ne '.') { @list = map { $path . "/" . $_ } @list; } # Find any import commands if (m|\\import\{([\w\/.-]+)}\{([\w\/.-]+)\}|) { push(@list,$1.$2); } # Find ALL files for make dependencies if ($all_files) { # Graphics files if (m|\\includegraphics(?:\[.*?\])?\{([\w\/.-]+)\}|) { # Add .eps if not present my $gfx_file = $1; if ($gfx_file !~ /\.\w+$/) { $gfx_file .= '.eps'; } if ($path ne '' and $path ne '.') { $gfx_file = $path . "/" . $gfx_file; } push(@list,$gfx_file); } # project-specific: # # CSR files (just look for the \CSRtable invocation) if (m|\\CSRtable|) { my $func_file; ($func_file = $filename) =~ s/\.tex$/.func/; push(@list, $func_file); } } # end all_files my $subfile; for $subfile (@list) { push(@file_list, get_tex_files($subfile, $all_files)); } } return @file_list; } # get_tex_files # Read a .tex file line by line, looking for register declarations # using the register.sty macro. This subroutine only examines # a single file. # # Arguments: filename # reference to array of register information # sub get_regs { my ($filename, $reg_data) = @_; my $file = new FileHandle "$filename"; # Record the function in which a register resides. Allow this to # be defined for just the first register in a file, then carried # over to all the others. my $function = 0; while (<$file>) { # find register definition if ( m/\\begin\{register\}\{[\w\!]+\}\{(.*)\}\{(.*)\}%(.*)$/ ) { my $long_name = $1; my $offset = $2; my $testable = 1; my $short_name = undef; my $remainder = $3; if ($remainder ne "") { if ($remainder =~ /don\'?t(_| )test/i) { $testable = 0 } if ($remainder =~ /NAME=([\w\\<>\d]+)/i) { $short_name = $1 } if ($remainder =~ /func=(\d)/i) { $function = $1 } if ($remainder =~ /off(?:set)?=([\w-,g]+)/) { $offset = $1 } } # get rid of escaped underscores $long_name =~ s/\\_/_/g; $short_name =~ s/\\_/_/g; my $width = 0; my $reg = { long_name => $long_name, short_name => $short_name, function => $function, offset => $offset, testable => $testable, fields => [] }; # now we need to extract fields. first, # read in rest of the reg macro @wholereg = (); while (<$file>) { push(@wholereg, $_); last if (/\\end\{register\}/); } # combine lines if nested {}'s do not match for (my $i = 0; $i <= $#wholereg; $i++) { my $open = grep(/\{/, split(//, $wholereg[$i])); my $close = grep(/\}/, split(//, $wholereg[$i])); if ($open != $close) { my ($next) = splice(@wholereg, $i+1, 1); $wholereg[$i] .= $next; redo; } } foreach (@wholereg) { if (m/\\label\{(.*)\}/) { $reg->{label} = $1; } # normal register field (reset value specified) if ( m/\\regfield\{(.*)\}\s*\{(.*)\}\s*\{(.*)\}\s*\{(.*)\}\s*%(.*)$/s ) { my $remain = $5; my $field_name = $1; my $field_len = $2; my $field_start = $3; my $reset = $4; $reset =~ s/^\{//; $reset =~ s/\}$//; $reset =~ s/_//g; my $reset_mask = $testable; # indicates field is deterministic after reset my $write_mask = $testable; # indicates field is modified by regbus writes if ($field_name =~ m/\{([\w ])\}$/) { $field_name = $1; } $field_name =~ s/\\_/_/g; if ($remain =~ /read(?:_| )only/i) { $write_mask = 0; } if ($remain =~ /status/i) { $reset_mask = 0; $write_mask = 0; } if ($field_name =~ /reserved/i) { $write_mask = 0; } if ($field_len > 32) { die "#ERROR Reg_macro Field of greater than 32 bits in $short_name!\n"; } push(@{$reg->{fields}}, { name => $field_name, len => $field_len, start => $field_start, reset => $reset, reset_mask => $reset_mask, write_mask => $write_mask }); } # register field without reset if ( m/\\regfieldb\{(.*)\}\s*\{(.*)\}\s*\{(.*)\}\s*%(.*)$/s ) { my $remain = $4; my $field_name = $1; my $field_len = $2; my $field_start = $3; my $reset_mask = $testable; # indicates field is deterministic after reset my $write_mask = $testable; # indicates field is modified by regbus writes if ($field_name =~ m/\{([\w ])\}$/) { $field_name = $1; } $field_name =~ s/\\_/_/g; if ($remain =~ /read(?:_| )only/i) { $write_mask = 0; } if ($remain =~ /status/i) { $reset_mask = 0; $write_mask = 0; } if ($field_name =~ /reserved/i) { $write_mask = 0; } if ($field_len > 32) { die "#ERROR Reg_macro Field of greater than 32 bits in $short_name!\n"; } push(@{$reg->{fields}}, { name => $field_name, len => $field_len, start => $field_start, reset_mask => $reset_mask, write_mask => $write_mask }); } } # end of register macro found print Dumper $reg if ($REG_DEBUG); push(@$reg_data, $reg); } } } # get_regs # Build a hash of registers, keyed on function and offset. That is, # given parsed register information, construct a hash with the # following structure # # $reg_hash { function } { offset } # # This will enable loops that iterate through registers in offset order # # Arguments: reference to array of reg information from get_regs # Returns: reference to constructed hash # sub build_reg_hash { my ($reg_array) = @_; my $reg_hash = {}; my $unknown_count = 0; # iterate through all defined registers foreach my $reg (@$reg_array) { if ($reg->{offset} =~ m/^(0x[0-9A-F]+)$/i) { # simple offset specified my $offset = $1; if (exists $reg_hash->{$reg->{function}}{$offset}) { print STDERR "##ERROR Reg_macro A Collision at offset $offset of function $reg->{function} \n"; print STDERR " Existing register: " . $reg_hash->{$reg->{function}}{$offset}{long_name} . "\n"; print STDERR " New register: " . $reg->{long_name} . "\n"; print STDERR "#END\n"; $REG_ERRORS++; } $reg_hash->{$reg->{function}}{$offset} = $reg; } elsif ($reg->{offset} =~ m/^(0x[0-9A-F]+)\s*-+\s*(0x[0-9A-F]+)/i) { # range specified. my $start = hex($1); my $end = hex($2); # determine stride my $stride = $REG_STRIDE; if ($reg->{offset} =~ m/,((?:0x)?[0-9A-F]+)/) { $stride = hex($1); } my $count = 0; for (my $index = $start; $index <= $end; $index += $stride) { my $offset = sprintf("0x%03x",$index); if (exists $reg_hash->{$reg->{function}}{$offset}) { print STDERR "##ERROR Reg_macro Collision at offset $offset of function $reg->{function} \n"; print STDERR " Existing register: " . $reg_hash->{$reg->{function}}{$offset}{long_name} . "\n"; print STDERR " New register: " . $reg->{long_name} . "\n"; print STDERR "#END\n"; $REG_ERRORS++; } # perform any substitutions in long_name my $reg_copy = { %{$reg} }; if ($reg_copy->{long_name} =~ m//) { $reg_copy->{long_name} =~ s//$count/; } else { $reg_copy->{long_name} .= " $count"; } # perform any substitutions in short_name if ($reg_copy->{short_name} =~ m//) { $reg_copy->{short_name} =~ s//$count/; } else { $reg_copy->{short_name} .= "$count"; } $reg_hash->{$reg->{function}}{$offset} = $reg_copy; $count++; } } else { # unknown offset $reg_hash->{$reg->{function}}{"unk".$unknown_count++} = $reg; print STDERR "#WARNING Reg_macro Register '$reg->{long_name}' has an unknown offset \n"; } } # end foreach return $reg_hash; } # build_reg_hash # Convert an ASCII string representing a binary number to a true decimal number sub bin2dec { return unpack("N", pack("B32", substr("0" x 32 . shift, -32))); } # Convert a true number to a binary ASCII string sub dec2bin { my $str = unpack("B32", pack("N", shift)); return $str; } # Covert an ASCII string representing a hex number to a binary ASCII string; sub hex2bin { return unpack("B32", pack("N", hex(shift))); } # Convert an ASCII string representing a binary number to a hex ASCII string; sub bin2hex { my $Bin = shift(@_); my $Hex; # Clean up string and look for errors chomp $Bin; if ($Bin =~ m/[^10]/) { print STDERR "#ERROR Reg_macro $Bin is not binary\n"; return (""); } return scalar reverse unpack "h*",(pack "b*", scalar reverse $Bin); } # required by Perl 1; ## Local Variables: ## mode: perl ## End: