diff options
Diffstat (limited to 'lib/compiler/assembler.tcl')
-rwxr-xr-x | lib/compiler/assembler.tcl | 595 |
1 files changed, 595 insertions, 0 deletions
diff --git a/lib/compiler/assembler.tcl b/lib/compiler/assembler.tcl new file mode 100755 index 0000000..5c41a28 --- /dev/null +++ b/lib/compiler/assembler.tcl @@ -0,0 +1,595 @@ +#!/usr/bin/tclsh +# Part of MCU 8051 IDE ( http://mcu8051ide.sf.net ) + +############################################################################ +# Copyright (C) 2007-2009 by Martin Ošmera # +# martin.osmera@gmail.com # +# # +# This program is free software; you can redistribute it and#or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation; either version 2 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program; if not, write to the # +# Free Software Foundation, Inc., # +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # +############################################################################ + +# -------------------------------------------------------------------------- +# DESCRIPTION +# Coverts precompiled source code to IHEX 8 format. +# +# This code is part of compiler (see compiler.tcl), precompiled code is +# generated by precompiler (see precomiler.tcl). +# +# -------------------------------------------------------------------------- +# OUTPUT FORMATS: +# - Intel® HEX 8 +# - Binary string +# - MCU 8051 IDE Simulator data file +# +# -------------------------------------------------------------------------- +# FORMAT DETAILS: +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# MCU 8051 IDE Assembler debug file +# hash file [hash file [hash file ...]] # comment +# filenum line address code [code [code ...]] # another comment +# ... +# hash - Hexadecimal MD5 hash of source code +# file - Filename of source code +# filenum - File number (begining from 0) +# line - Number of line in source code +# address - Code address +# code - Processor code +# +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Intel HEX 8 +# :LLAAAATTDD->CC +# ... +# ... +# :00000001FF +# LL - 8-bit length of data field (for instance '11' means 17 bytes) +# AAAA - 16-bit address +# TT - 8-bit type: +# 00 - data +# 01 - EOF (End Of File), ussualy ':00000001FF' +# DD-> - X x 8-bit data +# CC - checksum: +# 0x100 - LL - AA(h) - AA(l) - TT - DD ... +# -------------------------------------------------------------------------- + +namespace eval assembler { + + ## PUBLIC + variable hex ;# Output IHEX8 data + variable adf ;# Output simulator data + variable bin ;# Output binary data + variable error_count ;# Errors count + + ## PRIVATE + variable fileNumber ;# File number + variable lineNumber ;# Number of line currently beeing parsed + variable offset ;# Current address + variable data_len ;# Data length (for creating IHEX records) + variable data_field ;# Data field (for creating IHEX records) + variable address ;# Address of begining of IHEX data field + variable operands ;# List of operand codes + variable opcode ;# OP code of the current instruction + variable bin_len ;# Lenght of binary data + variable opr_types ;# List of operand types + variable included_files ;# List: Unique unsorted list of included files + variable working_dir ;# String: Project working directory + + + # ---------------------------------------------------------------- + # GENERAL PURPOSE PROCEDURES + # ---------------------------------------------------------------- + + ## Compile the given code + # @parm String md5 - Source code MD5 (hexadecimal hash) + # @parm String date - Current date + # @parm String project_dir - Project directory + # @parm String name - Name of source file + # @parm List files - List of included files (1st one is primary source file) + # @parm List code - Precompiled source code (code generated by preprocessor) + # @return Bool - result (1 == success; 0 == fail) + proc compile {md5 date project_dir name files code} { + variable working_dir $project_dir ;# String: Project working directory + variable included_files $files ;# List: Unique unsorted list of included files + variable fileNumber 0 ;# File number + variable lineNumber 0 ;# Number of line currently beeing parsed + variable opcode ;# OP code of the current instruction + variable operands ;# List of operand codes + variable address ;# Address of begining of IHEX data field + variable opr_types ;# List of operand types + variable offset 0 ;# Current address + variable data_len 0 ;# Data length (for creating IHEX records) + variable data_field {} ;# Data field (for creating IHEX records) + variable hex {} ;# Output IHEX8 data + variable adf {} ;# Output simulator data + variable bin {} ;# Output binary data + variable error_count 0 ;# Errors count + variable bin_len 0 ;# Lenght of binary data + + # Local variables + set idx -1 ;# Current line in precompiled code + set nolist 0 ;# Bool: Do not crete record in code listing for the current line + set pointer 0 ;# Expected address + + # Create header of simulator data + if {${::Compiler::Settings::CREATE_SIM_FILE}} { + set filename [file join $project_dir $name] + set project_dir_len [string length [file normalize $project_dir]] + if {[string first $project_dir $name] != -1} { + set filename [string replace $filename 0 $project_dir_len] + } + + set adf "# Assembler debug file for ${::APPNAME}\n" + append adf "# Used assembler: MCU 8051 IDE\n" + append adf "# Date: $date\n" + set project_dir_len [string length $project_dir] + foreach filename $included_files { + if {[catch { + append adf [::md5::md5 -hex -file $filename] + } result]} { + append adf 0 + CompilationError [mc "File access error:\n%s" $result] + } + if {![string first $project_dir $filename]} { + set filename [string replace $filename 0 $project_dir_len] + } + append adf { "} $filename {" } + } + } + + # Iterate over precompiled code + foreach line $code { + + # Update GUI after each 25 iterations + if {[expr {$idx % 25}] == 0} ${Compiler::Settings::UPDATE_COMMAND} + if {${::Compiler::Settings::ABORT_VARIABLE}} { + ${::Compiler::Settings::TEXT_OUPUT_COMMAND} [::Compiler::msgc {EN}][mc "Aborted"] + free_resources + return + } + + incr idx + set nolist 0 + + # Parse line + set lineNumber [lindex $line 0] ;# Line in source code (dec) + set fileNumber [lindex $line 1] ;# Number of file + set address [lindex $line 2] ;# Address (dec) + set instruction [lindex $line 3] ;# Instruction name (or directive) + set operands [lindex $line 4] ;# List of operands + set opr_types [lindex $line 5] ;# List of operand types + + # Directive 'DB' + if {$instruction == {db}} { + set len 1 + set opcode [lindex $operands 0] + set mask 0 + set operands {} + if {$opcode == { }} { + set opcode 32 + } + set opcode [format %X $opcode] + set digits [string length $opcode] + if {$digits < 2} { + set opcode "0$opcode" + } elseif {$digits > 2} { + CompilationError [mc "Unknown error %s" 100] + continue + } + + set nolist 1 + } { + # Check for instruction existence + if {[notAnInstruction $instruction]} {continue} + + # Find matching operand set + set definition [find_instruction_definition $instruction] + if {$definition == {}} {continue} + set len [lindex $definition 1] + set opcode [lindex $definition 2] + set mask [lindex $definition 3] + } + + # First line + if {$pointer == 0} { + set offset $address + } + + # If data length overlaps 255 or adress is too high -> flush data buffer + if {($pointer < $address) || (($data_len + $len) > ${::Compiler::Settings::max_ihex_rec_length})} { + write_bin + write_hex + set data_len 0 + set offset $address + set pointer $address + + # Unexpected adress + } elseif {$pointer > $address} { + CompilationError [mc "Invalid address at %s" 0x[format %X $address]] + set pointer $address + continue + } + + incr pointer $len ;# Expected address + incr data_len $len ;# Data length + + # + ## Calculate processor code + # + + # Translate operands to 16|12|8-bit hex + set operands [oprs2hex] + + # Adjust inctruction OP code acording to the OP code mask and operands + if {$mask != 0} { + # Determinate length of the first operand + set mask_bin [hex2binlist $mask] + set opr_len 8 + foreach bit $mask_bin { + if {$bit} {incr opr_len} + } + + # Translate OP code to list of booleans + set opcode [hex2binlist $opcode] + # Determinate the 1st operand + set operand0 [NumSystem::hex2bin [lindex $operands 0]] + + # Adjust operand + set true_opr_len [string length $operand0] + if {$opr_len > $true_opr_len} { + set operand0 "[string repeat 0 [expr {$opr_len - $true_opr_len}]]$operand0" + } elseif {$opr_len < $true_opr_len} { + CompilationError [mc "Unknown error %s" 101] + continue + } + incr opr_len -9 + set opcode_opr [string range $operand0 0 $opr_len] + incr opr_len + set operand0 [string range $operand0 $opr_len end] + + # Adjust OP code + set idx 0 + foreach mask_bit $mask_bin { + if {$mask_bit} { + set opcode [lreplace $opcode $idx $idx [lindex $opcode_opr $idx]] + } + incr idx + } + + # Finalize + set opcode [NumSystem::bin2hex [join $opcode {}]] + if {[string length $opcode] == 1} { + set opcode "0$opcode" + } + set operand0 [NumSystem::bin2hex $operand0] + if {[string length $operand0] == 1} { + set operand0 "0$operand0" + } + set operands [lreplace $operands 0 0 $operand0] + } + + # Write simulator data line + write_adf + + # Append processor code to data_field + set opcode [string toupper $opcode] + + # Exception for instruction MOV addr, addr (Reverse order of operands) + if {$opcode == {85}} { + append opcode [lindex $operands 1] + append opcode [lindex $operands 0] + # Other instructions + } { + foreach opr $operands { + append opcode $opr + } + } + append data_field $opcode + + # Write line to code listing + if {!$nolist} { + CodeListing::set_opcode $idx $opcode + } + } + + # Finalize + write_bin + write_hex + if {${::Compiler::Settings::OBJECT}} { + append hex {:00000001FF} + } + + # Return result + if {$error_count} { + return 0 + } { + return 1 + } + } + + ## Free resoureces (should be called after each compilation) + # @return void + proc free_resources {} { + foreach var { + hex adf bin + lineNumber offset data_len + data_field address operands + opcode bin_len error_count + fileNumber + } { + set ::assembler::$var {} + } + } + + + # ---------------------------------------------------------------- + # INTERNAL AUXILIARY PROCEDURES + # ---------------------------------------------------------------- + + ## Convert the given hexadecimal value to list of booleans + # @parm String value - Hexadecimal number to convert + # @return List - Resulting list of booleans (eg. {0 1 0 1 1 1 0 1}) + proc hex2binlist {value} { + set value [NumSystem::hex2bin $value] + set len [string length $value] + if {$len != 8} { + set value "[string repeat 0 [expr {8 - $len}]]$value" + } + return [split $value {}] + } + + ## Write line to simulator data string + # @return void + proc write_adf {} { + variable adf ;# Output simulator data + variable fileNumber ;# File number + variable lineNumber ;# Number of line currently beeing parsed + variable address ;# Address of begining of IHEX data field + variable opcode ;# OP code of the current instruction + variable operands ;# List of operand codes + + if {!${::Compiler::Settings::CREATE_SIM_FILE}} {return} + + # Convert operands to decimal + set new_code {} + lappend new_code [expr "0x$opcode"] + + # Exception for instruction MOV addr, addr (Reverse order of operands) + if {$opcode == {85} && [llength $operands] == 2} { + lappend new_code [expr "0x[lindex $operands 1]"] + lappend new_code [expr "0x[lindex $operands 0]"] + # Other instructions + } { + foreach hex $operands { + if {[string length $hex] == 4} { + lappend new_code [expr "0x[string range $hex 0 1]"] + lappend new_code [expr "0x[string range $hex 2 3]"] + } { + lappend new_code [expr "0x$hex"] + } + } + } + + # Append new value + append adf "\n" $fileNumber { } $lineNumber { } $address { } $new_code + } + + ## Write data field as binary string + # @return void + proc write_bin {} { + variable offset ;# Current address + variable data_field ;# Data field (for creating IHEX records) + variable data_len ;# Data length (for creating IHEX records) + variable bin ;# Output binary data + variable bin_len ;# Lenght of binary data + + if {!${::Compiler::Settings::CREATE_BIN_FILE}} {return} + + # Create padding + if {$offset > $bin_len} { + set diff [expr {$offset - $bin_len}] + append bin [string repeat "\x00" $diff] + incr bin_len $diff + + } elseif {$offset < $bin_len} { + CompilationError [mc "Unknown error %s" 102] + } + + # Write binary data + for {set i 0; set j 1} {$i < $data_len} {incr i 2; incr j 2} { + set hex [string range $data_field $i $j] + append bin [subst "\\x$hex"] + incr bin_len + } + } + + ## Write data field as IHEX record + # @return + proc write_hex {} { + variable data_len ;# Data length (for creating IHEX records) + variable offset ;# Current address + variable data_field ;# Data field (for creating IHEX records) + variable hex ;# Output IHEX8 data + + if {!${::Compiler::Settings::OBJECT}} {return} + + # Create fields length and address + set len_field [dec2hex $data_len 2] + set addr_field [dec2hex $offset 4] + + # Create IHEX8 record + set line ${len_field} + append line ${addr_field} {00} ${data_field} + set check_field [::IHexTools::getCheckSum $line] + append line $check_field + + # Append created record to resulting string + append hex {:} + append hex $line + append hex "\n" + + # Reset data field + set data_field {} + } + + ## Convert decimal value to hexadecimal string + # @parm Int value - Number to convert + # @parm Int length - Length of resuting hexadecimal string + # @return String - result + proc dec2hex {value length} { + + # Convert and determinate length + set value [format %X $value] + set true_length [string length $value] + + # Adjuts length + if {$true_length < $length} { + return "[string repeat 0 [expr {$length - $true_length}]]$value" + } elseif {$true_length > $length} { + incr length -1 + return [string range $value "end-$length" end] + } else { + return $value + } + } + + ## Translate current operands to list of hexadecimal strings + # @return List - result (list of two digits pairs) + proc oprs2hex {} { + variable operands ;# List of operand codes + variable opr_types ;# List of operand types + + # Initialize result + set new_operands {} + + # Iterate over given operands + foreach opr $operands type $opr_types { + # Check for variable operand type + if {[lsearch ${::CompilerConsts::FixedOperands} [string tolower $opr]] != -1} {continue} + + # Check for validity of the given operand + set opr [string trimleft $opr {#@/}] + if {![regexp {^\d+$} $opr]} { + CompilationError [mc "Invalid operand: '%s'" $opr] + break + } + + # Convert to hexadecimal string + set opr [format %X $opr] + + # Adjust length + set opr_len [string length $opr] + if {($type == {imm16}) || ($type == {code16})} { + if {$opr_len < 4} { + set opr "[string repeat 0 [expr {4 - $opr_len}]]$opr" + + } elseif {$opr_len > 4} { + CompilationError [mc "Invalid value"] + } + + } elseif {$type == {code11}} { + if {$opr_len < 3} { + set opr "[string repeat 0 [expr {3 - $opr_len}]]$opr" + + } elseif {$opr_len > 3} { + CompilationError [mc "Invalid value"] + } + + } elseif {$opr_len == 1} { + set opr "0$opr" + } + + # Append operand to the resulting list + lappend new_operands $opr + } + + # Return result + return $new_operands + } + + ## Search for operands definition list of the given instruction (instruction must be valid) + # @parm String instruction - Name of instruction to find + # @return List - Found operands definition list + proc find_instruction_definition {instruction} { + variable opr_types ;# List of operand types + + # Iterate over operands definitions + foreach definition [lindex $CompilerConsts::InstructionDefinition($instruction) 1] { + + # Determinate operand types + set opr_list [lindex $definition 0] + + # Compare the given operand types with definition + set match 1 + foreach given_type $opr_types possible_type $opr_list { + if {[lsearch $given_type $possible_type] == -1} { + set match 0 + break + } + } + + # Return result + if {$match} { + return $definition + } + } + + # Nothing found + if {!$match} { + CompilationError [mc "Invalid operand"] + return {} + } + } + + ## Verify validity of the given instruction (Invokes CompilationError of fail) + # @parm String instruction - Instruction to verify + # @return Bool - result + proc notAnInstruction {instruction} { + if {[lsearch ${::CompilerConsts::AllInstructions} $instruction] == -1} { + CompilationError [mc "Unknown instruction: %s" $instruction] + return 1 + } + return 0 + } + + ## Compilation error + # @parm String errorInfo - Error string + # @return void + proc CompilationError {errorInfo} { + variable working_dir ;# String: Project working directory + variable included_files ;# List: Unique unsorted list of included files + variable error_count ;# Errors count + variable fileNumber ;# File number + variable lineNumber ;# Number of line currently beeing parsed + + incr error_count + set filename [lindex $included_files $fileNumber] + if {![string first $working_dir $filename]} { + set filename [string replace $filename 0 [string length $working_dir]] + } + if {[regexp {\s} $filename]} { + set filename "\"$filename\"" + } + set filename [mc " in %s" $filename] + if {${::Compiler::Settings::WARNING_LEVEL} < 3} { + if {${::Compiler::Settings::NOCOLOR}} { + ${::Compiler::Settings::TEXT_OUPUT_COMMAND} \ + [::Compiler::msgc {EL}][mc "Compilation error at line %s: %s" "${lineNumber}${filename}" $errorInfo] + } { + ${::Compiler::Settings::TEXT_OUPUT_COMMAND} \ + [mc "\033\[31;1mCompilation error at line \033\[31;1;4m%s\033\[m%s: %s" $lineNumber $filename $errorInfo] + } + } + } +} |