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 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617
|
#!/usr/bin/tclsh
# Part of MCU 8051 IDE ( http://https://sourceforge.net/projects/mcu8051ide/ )
############################################################################
# Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 by Martin Ošmera #
# martin.osmera@gmail.com #
# #
# Copyright (C) 2014 by Moravia Microsystems, s.r.o. #
# 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. #
############################################################################
# >>> File inclusion guard
if { ! [ info exists _ASSEMBLER_TCL ] } {
set _ASSEMBLER_TCL _
# <<< File inclusion guard
# --------------------------------------------------------------------------
# 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 (beginning 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 beginning 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 beginning 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]} then {
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
} else {
# 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 address 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 address
} 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 instruction OP code according 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 op_idx 0
foreach mask_bit $mask_bin {
if {$mask_bit} {
set opcode [lreplace $opcode $op_idx $op_idx [lindex $opcode_opr $op_idx]]
}
incr op_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
} else {
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}
append hex "\n"
}
# Return result
if {$error_count} {
return 0
} else {
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 beginning 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
} else {
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]"]
} else {
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 -nocommands "\\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 {![string is digit -strict $opr]} {
CompilationError [mc "Invalid operand: '%s'" $opr]
break
}
# Convert to hexadecimal string
set opr [string trimleft $opr 0]
if {$opr == {}} {
set opr 0
} else {
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 %s: %s" "${lineNumber}${filename}" $errorInfo]
} else {
${::Compiler::Settings::TEXT_OUPUT_COMMAND} \
[mc "\033\[31;1mCompilation error at \033\[31;1;4m%s\033\[m%s: %s" $lineNumber $filename $errorInfo]
}
}
}
}
# >>> File inclusion guard
}
# <<< File inclusion guard
|