summaryrefslogtreecommitdiff
path: root/lib/compiler/assembler.tcl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/compiler/assembler.tcl')
-rwxr-xr-xlib/compiler/assembler.tcl595
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]
+ }
+ }
+ }
+}