Operation of JSIM-51

JSIM51 is a powerful tool for software development with 8051-Controllers and it's derivatives.
The program simulates the processor kernel and some of the hardware functions.
I wrote it because all commercial products are too expensive for private users.
Obviously, as for all freeware, there is no guarantee for the correct function in all cases.
But I'll try to fix all bugs which I'll get reported as fast as possible.
The following text explains the most important functions and the operation of this program.

Download 8051.zip ( DLL for simulation of a standard-8051 , V1.017 - 09/17/1998 122kB)
80320.zip ( DLL for simulation of a 80320 -Dallas , V1.019 - 10/03/2000 112kB)
jsim_e.zip ( english version V4.05 from 01/08/2000, 286kB )

Unfortunately I've no time for further support of the program. So if anybody wants to have anything
changed, he must do it self. Here are the sources.
I would expect that useful changes will be make accessible for other people! 

Download latest sources for the core application  (coresrc30.zip)
Download latest sources for the 8051-DLL (8051src22.zip)
Download latest sources for the 80320-DLL (80320src14.zip

Here is a short project to test jsim51 testprj.zip (3k)



This program simulates the processor kernel and some hardware functions of a 8051 controller. In this 
way it's possible to test software parts independently of the target platform. The speed of this simulator reaches
( running on a Pentium 200 ) nearly that of an 8051 clocked with 10MHz (in the best case without trace). I think this is
acceptable. The operation philosophy and user interface is designed as far as possible and sensible like
that from MS-VisualC++ 4.2. You need the newest version of Comctl32.dll which comes with IE3 !
Because the program is fairly complex, I expect to still find some bugs. Due to bug fixing and improvements, the operation
may differ from this manual
To facilitate operation by the user, most elements of the interface have tooltips. All windows can be docked
in the main frame. In this way the surface becomes more comfortable and orderly.
Because I was always disgruntled if a program used many DLLs and wrote something in the registry, I was 
careful  to avoid that.  Till version 2.09 it was so. However, many people want to simulate different derivatives
of the 8051 or possibly other controllers with the same operation interface. So I've divided the application into an EXE (jsim.exe)
and at least one DLL which contain the simulation program for a specific processor. Otherwise there are no changes.
There are no entries in the registry. If you want to dispose the simulator, simply delete Jsim.exe its INI-file and the processor DLLs.
The settings of any project will be written in a file with the extension ".wsp" which is located in the project directory.
The workspace file from version 3.0 are not compatible with the older versions. Also  workspace files created from the German
version may not work under the English version and vice versa.

If you want to know more about the internals of JSIM51 read this page (in German):
Hintergründe zum Programm und Implementierungshinweise ! 

 Select a processor
The processor which is to be simulated must be loaded as a DLL since version 3.00. Do this with:File-Select prozessor-> selection. 
In the dialog you can select a processor or search for a DLL. The available processor DLLs will be
registered in "jsim.ini" and are visible in the list. (At the first start you must search for the DLLs!).
The type of the selected processor will be noticed in the workspace file. You must select only one time explicitly.
If at the time of opening of a workspace a processor was loaded, it will be taken instead of the type in the workspace file!

  Load  files
JSIM can load the following file-formats:

 OMF-51Ext is supported only by the Keil-C51-compiler. This file format should be preferred. It yield more profit at debugging HLL-sources. (distinction between upper/lowercase of symbols, type definitions for complex data types). I've tested the HLL-debugger with sources which were compiled with the Keil-C51 compiler V3.40 By now the version 5.1 is available. I' m not sure if the debug information is always represented in the same way. This simulator doesn't support bank linked applications as the new compiler and linker. The simple OMF51-format knows only uppercase symbols. Because a symbol has no information about it's type, the only known information is it's address but not it's size and structure.

An absolute file will be opened with "file-open->selection". The source files must be located in the same directory as the absolute file. If not you must configure the path by hand in the workspace file under "SourcePath=..." This path is valid for all sources at once. Since version 2.04 you can load files also via drag-drop. The file will be read record by record. For each contained module (source file) a entry in the browser window is created. This entry has the name of the associated module. If the last modification date of a source file is newer than the absolute file, a warning is displayed. If for this reason the association between source file and line symbol cannot set correctly, the program may crash if you try to display in MIX-mode. Procedures which are defined inside a module will be displayed as sub entries below the module. Library procedures and code which is generated by the compiler will be displayed as an separate module with the name "LIB".

 Load a HEX-file Sometimes it may necessary to also load a hex-file. But this file doesn't contain symbol information. That means at reassembling also constants are translated to program code, what is nonsense. Nevertheless you can debug without symbol information. A hexfile can be loaded also before a absolute file. Then the code memory isn't cleared. Open a hexfile from menu with "file-open->selection". JSIM51 remembers if you load a hexfile before an absolute file and try to load it again with the project at the next time. If the hexfile isn't found it will be removed from the workspace file. (You can do it by hand if you delete the key "hexfile=")

   Saving the actual workspace
All actual visible windows, watches, breakpoint etc. can be saved with "file-save as->selection". The file is saved in ASCII-format.
The default name of this file is the name of the absolute file and the extension ".wsp". Nevertheless you have the opportunity to 
choose any name. This file contains all information to restore the actual settings. It could be edited by hand.
 But be carefully. Many entries related to window positions, IDs and docking state are very mysterious and depend from each
other. If the program crashes while loading it's recommendable to delete or rename first this workspace file.
The  program starts then with it's default settings. Watch expressions or breakpoints can be taken from the corrupted WSP-file 
without risk.
At loading an absolute file (without the extension .wsp), the program looks first to a matching wsp-file. If found it restores the display.
Note that code changes could cause that a breakpoint will become invalid.

  Browser-window and displaying the loaded code
As start window the module which contain the symbol "main" will be searched and displayed. Is this symbol is undefined, the 
code window from the reset address (0x0000) will be displayed. Normally (ie. C-programs) this is the  "LIB"-window and contain
assembler code.
Double click on a module or procedure in the browser window switches the actual code view to this program point.
Each module own its private window which will be activated if required or necessary. 
If the displayed program part is written in "C" ( and the source file is available)  the source text is displayed. 
Alternatively you can switch to the display modes Mixed(MIX) and Assembler(ASM). This can be done via menu or toolbar.
If the source text isn't available ore the code don't contain HLL information, the buttons  "MIX" and "HLL" are disabled.  
Code areas which are not loaded via absolute file can be shown at assembler level. The input of the address must be done on 
the command input line ("d.p. address"). From this address the contents of the code memory is reassembled and displayed
in a window named "UnKnown". To discern from loaded code the window has a different background colour. The view can be scrolled
only forward. If the window will be closed and reopened the code will be reassembled each time. ( Maybe anybody builds self
modifiable code ).The display can be switched alternatively from the command line if you enter a address or label. 
  The state of the displayed modules (size, position, view mode) won't be stored in the workspace file.

d.p.0x380   -shows code at address 0x380
d.p.main   -shows code at label "main"
If no opcode is at the address,  the cursor is set to the next HLL-line or assembler command.  

   Run the code...

Operation can be done via keyboard or toolbar. The actual position of the program counter is marked with a yellow arrow.
(  The cursor is a grey arrow ) The consequence of the commands depends on the actual viewmode.

Step-In  (F11):
Assembler- and Mixmode   - Only one command will be executed. Is the command a "call" the jump into the procedure will executed.
HLL   - One HLL-line will be executed. That means, assembler commands will be executed until the program counter reach a address which is associated to a HLL-line. This could be the next following HLL-line or a line in another procedure/module.
Step-Over  (F10):    
Assembler- and Mixmode   - If the assembler command is NOT a "call" Step-Over be effective the same as Step-In. The command is executed and the PC shows after that to the address of the next assembler command. If the command is a "call", to the address of the next command (after the call) a temporary breakpoint is set. The program is started in free run. If the breakpoint is reached the program execution is stopped and the breakpoint will be deleted. In this manner many subroutines can be nested and the execution does not stop.
Important to comply with that, the program doesn't stop if the command after the "call" is never reached.
This could happen if inside the subroutine the stack will be changed and the return lead to another address.
HLL   -Contains the HLL-line no procedure call, then Step-Over cause the same as Step-In at HLL-level. If, inside of the line, a procedure is called, the program executes assembler commands till the program counter reaches a HLL-line in the same procedure. This condition will be checked after each instruction. Therefore such Step-over is very slow.
The reason, not to search the next instruction as at assembler code is e.g. the "goto" command. The target of the "goto" is unknown but normally not the next HLL-line.

Run-To-Cursor (F8)  
Assembler- and Mixmode   - To the address at the cursor line a temporary breakpoint is set. The program is started in free run. It runs till the next breakpoint. ( This may be also another active breakpoint ) If the temporary breakpoint is reached, it is cleared and the program stops execution.
Run  (F5)  
Assembler- ,
Mix- and HLLmode
  - The program starts without break condition. It executes as long as no break is reacted nor the user cancels execution via Stop.
Stop (F6)   
Assembler- ,
Mix- and HLLmode
  - The program execution is terminated immediately. The code window scrolls to the actual program counter.


Next to the internally administrated temporarily breakpoints, the user has the opportunity to set breakpoints at any location
in the program. Breakpoints are marked with an read point in code window. They can be set, cleared (F9), or toggled (CTRL-F9)
directly in this window. This is possible in all display modes. In Mixed mode a breakpoint is set on assembler instructions.
Alternatively you can set breakpoints from the command input line. But the address must be a symbolic label.
The reason is as follows: breakpoints are implemented by inserting an illegal opcode (0xA5) at the break address.
If you have the possibility to set a break at any numeric address, you could change a data byte and not a op-code.
The result may be fatally: The break would never be reached but your program may do any unpredictable actions.  
 Since version 1.10 breakpoints are represent by an additional byte for each byte in code memory. Even so the above 
description isn't changed.
  Since version 2.00 you can set breakpoints also on memory access. You can decide if on read, write or both.
This can be done in the following way:

  - In the opened memory window  (only possible in BYTE or ASCII view !!)  
1. Select the interesting Byte with click on the left mouse button.
2.1 With right mouse click open the context menu. Select the read, write or both.
2.2 ... or set it with CTRL-R (read-breakpoint) or CTRL-W (write breakpoint)
3 A active breakpoint is marked orange.(Breakpoints on program execution are
not visible in the memory window)
- From the command line with this syntax:
b.s address(hex) access type memory range      
b.s 0xF014 0x200 c   set a read access breakpoint to address 0xF14 in CODE-memory
b.s 0x14 0x400 d   set a write access breakpoint to address 0x14 in DATA-memory
b.s 0x95A 0x600 x   set a write+read access breakpoint to address 0x95A in XDATA-memory
If you set only one access breakpoint or changes the default memory configuration, then the simulation runs
another, very slower path. The reason is, each access must be checked now if it reaches a break.
This cause a decrease  about 70-80% !!!. The number of breakpoints doesn't  affect the speed anymore if one is set.

Breakpoints can be deactivated. This is possible for the breakpoints at all, from menu with  Edit-Breakpoints->Selection-Toggle
or at the cursor position with CTRL-F9. Breakpoints are stored in the workspace file in its actual state.
b.s.main   set breakpoint at address "main"

The register window will be opened from menu with "View-Register", via toolbar button or from command line with "w.r".
All important processor registers are displayed here and can be modified directly. Enter the new value and press "ENTER".
If a register is changed while program execution, it is displayed in red after the next stop. On principle the window is updated 
after each program stop, after step and if a value in another memory or watch window is changed.
  If the PC would changed in the register window, the code window doesn't switch immediately but first after the next step !
 The output of the register P2 can't be read back really. The simulator assumes that in- and output of the register are 
the same. Normally this should not disturb because this port isn't available if external memory is used.

The memory window will be opened from menu "View-Memory". Maximal 10 memory windows can be opened at the same time.
This should be enough. Alternatively you can use the command line: "w.m.viewmode.address /memoryspec".
The address can be entered as decimal or hexadecimal. Also a label or a qualified C-expression is possible. That may cause 
the contents where the window is pointed to, to change continuously.
Important ! You must insert at least one space before "/". 
The viewmode is specified with the following characters:  
b Byte
s short(16Bit)
l long(32Bit)
The memoryspec can have this values:
c (oder keine Angabe) CODE
(If P2 is changed, the region where the window is pointed to
is also changed)
0x1F00 /x   - Xdata from 0x1F00
20 /d   - Data from 0x14
testarray   - Memory at symbol "testarray".
*xptr+2 /c   - Code from the content of the pointer xptr.
Such an expression can be used only if OMF51-Ext
was loaded. Otherwise you don't know the type of the
pointer and its referenced memory.
MAIN   - Code memory at Symbol MAIN.
Click with the right mouse button opens a context menu for the view modes.    
In all modes you can change the values directly. To do this, click at the entry and enter the new value. The value is assumed
as hexadecimal value. In ASCII-mode the keyboard code will be entered. Confirm with "ENTER". Is the input valid then the new 
value will be written in memory. Memory contents which was changed since the last window update will be displayed red.
So also at step through a program which changes the currently displayed memory region.
 Copy and Paste in memory window
It's possible to change a memory region all at once with Copy-Paste. To enter a string the view mode must be ASCII.
Select the cell from where the string should be written. The string is inserted as usual with Ctrl-V, SHIFT-Insert or
via context menu. The closing zero is also copied ! Should be inserted binary values the view mode must be BYTE and  
the string must be written in this form: "01 dd E5 77 00 FF". The procedure is the same as for ASCII strings.


JSIM51 offers powerful possibilities to display not only the values of any variables but also the evaluation of complex 
C-expressions. There are different ways to display values of variables and expressions:

Simply hold the mouse pointer over the name of a variable or mark it with double click. After a short time the actual 
value is displayed. If it is a built in data type the value is shown. If it is a pointer, the memory space where it is pointed to
and it's value is shown. In case of a complex type,  e.g. a structure, its start address and memoryspace in braces. If the label is 
unknown the tooltip doesn't appear.
 This kind of displaying variables looks for a local variable with the selected name, i.e. the variable name will be expanded
to the form module:procedure:variable name. To get the module and procedure the actual cursor position is used. If the
cursor is outside of code, (e.g. on a declaration) the first variable with this name in the displayed module is searched and displayed.
This is, if present the global variable or any other local symbol with this name.
  Tooltip on marked text
If the expression is more complex (e.g. testval[ x ] ), select it with the mouse and set the mouse pointer on the selected text.
The display is as above only with the important difference that the name isn't expanded ! 

  Temporarily watch dialog 
The representation by tooltips allows only simple data types. If you want to see a structure or the content of a pointer open a
temporary watch window. Select a expression and press SHIFT-F9. The structure will be displayed as a tree. Elements which 
have a "+" can be opened furthermore with click on that symbol. Simple data types and pointers can be edited directly. Enter 
the new value and confirm with "ENTER". If you want to take the expression into the permanent watch window click on:
"Add to Watchwindow".
 Permanent Watchwindow
The watch window can be opened with "View-Watch", from toolbar or alternatively from the command line with "w.w".
A new watch expression is entered either from the temporary watch dialog or directly in the lower empty line. Spaces in an
expression are allowed. If the expression is valid, it will be evaluated and displayed. On the other hand a failure message is displayed.
( Note, a invalid expression must not always stay invalid. e.g. generic pointers by Keil-C51 )
You can edit a expression. Confirm always with "ENTER". Remove a entry by clearing the field and "ENTER". If a tree is opened 
the entries below are also removed. To differ global and local symbols, you can additionally enter module name and procedure 

Examples of watch expressions:  
x   - the variable x, if more than one variables exist with this name it is the global defined or the first which was found (also insecure)
_UP1   - the variable x, defined in the procedure UP1.
sisa[4].perfdat->BERcnt[x-1]   - a complex expression which can contain more than one variable.
{0x0200,x}   - the contents of the memory cell 0x200 in XDATA memory, displayed as Byte.
( the address can be entered only hexadecimal )
This is not a valid C-expression but can't be confused and makes it possible to display
the content of memory cells.
The display of the evaluated expressions is the same as in the temporarily watch dialog.

{XD: 0x3450}   - The address of a complex data type (not editable but mostly it can be opened)
{-> D: 0x55}   - Pointer to the address 0x55 in the data memory (editable and it can be opened)
0x42 "Beta"   - A simple character (signed char). To make string more readable the string from this address till the first 0 is displayed. Only the first character can be edited.
0x61   - A unsigned char. (editable)
0x1234   - short (editable)
You can open a context menu with the right mouse button. From there the display can be changed between decimal
and hexadecimal. A window update will be done after each code step or a change in a memory- or register window.
 OMF-51 doesn't know  PDATA-variables. I.e. a variable declared in PDATA appears in the watch window as
a XDATA symbol. This may be correct as long as you don't change register P2. To solve this problem open a context
menu (left column, unselected text !!). Then you can change the memory space between XDATA and PDATA.
Also possible from the command line with "c.p smybolname"  change to PDATA or "c.x smybolname" change to XDATA. 
After that, the watch follows the register P2. This setting will not be saved in the workspace. 
 Data types without "OBJECTEXTEND"
If an object file wasn't built with the option "OBJECTEXTEND", no information about the types of all symbols are available.
Not any of the compatible opportunities to display complex types described above are available. To make debugging 
more easy nevertheless, you can assign a simple data type to a symbol. At first insert the symbol in the watch window.
Then set the mouse pointer on this field and open with the right button the type assign dialog. The following types can be
signed char
unsigned char
signed short
unsigned short
signed long
unsigned long
Pointer to data types which were defined before in any memory space .
(The generic Keil-C51-pointer are not supported in this mode). If you want to use it compile your sources with the option
"OBJEXTENDED". A type can be changed at any time while debugging but it won't be stored in the workspace file.
This opportunity to manipulate the data types is only available if the absolute file doesn't contain the extended object file
format, i.e. if all modules were compiled without "OBJECTEXTEND".

A very comfortable service as at MS-Visual-C++. The window always shows all local variables of the actual procedure.
I.e. the displayed variables changes each time if the procedure is changed  as result of a call or return. Open this window 
from menu with "View-Locals", from toolbar or alternatively from command line with "w.l". You can't enter additional entries
into this window but you can edit the displayed variables.  


The breakpoint window will be opened from menu with "Edit-Breakpoints". It shows all actual set breakpoints.
A breakpoint can be deleted, activated or deactivated here. Inactive breakpoints are displayed with a red circle, active
with a red point.

Memory configuration window

This window will be opened from menu with "Edit-Memory configuration"
Here you can define the memory regions which are apparently available to a program. Any segmentations
are possible. In this way it's possible to detect a access into regions that are not available later on the
target hardware. Note, any changes in the default memory configuration has
consequences to the
simulation speed. It will be slow down if the memory access must be checked.


The analyser offers some options to measure the runtime of a program and to configure the trace.
The processor clock defines the oscillator frequency of the simulated target. The time in the window "real runtime"
is depend from this value. A reduction of the value immediately cause a increase of the runtime.
The value of "simulation time" is more informative. It's depend from the trace options and the computer where the 
program runs.
"Autostart" cause a reset of the cycle counter at each Step, Step-Over, Run etc. In this way it's possible to  measure 
the runtime for single C-instructions till single assembler commands. If "Autostart" isn't set, the periods will be accumulated.
The counters can be cleared with "Reset counter". The calculation of the simulation time will be done only if "Autostart" is set.

If you need information about the maximal, average and minimal runtime of a program part, (perhaps at loops over different
paths) you can set a measure point. It must be enabled in the analyser window ("measure point ON"). All time when the point 
is reached, the value of the actual cycle counter will be stored. The cycle counter is cleared on each, so also on inactive ( ! ),
breakpoint. Configure a measure as follows:
1. Set a inactive breakpoint to the entry point of the loop.
2. Set the measure point at the interesting point. ( F3 in code window, or from toolbar )
3. Set a breakpoint behind the loop.
4. Activate the measure in the analyser window an run the program.
When the last breakpoint is reached the number of  measures, the shortest, the average and the longest runtime of the loop is
There are two kinds to write the trace:
Each assembler instruction with the important registers.( PC- PSW- A- R0..R7- B- DPH- DPL- P2- SP )
For a selected program address (trace point) the value of any variables. On principle it would be no problem to realise 
more than only one trace point. But this would cause the simulation to slow down. I think one tracepoint is enough.
Often you want to know only the sequence of a value or port at a given program point. 
To use a trace point you must do two things:
1. Open the trace entry window with  "Edit-Tracepoint" and enter the interesting variables.
2. Set the trace point in code window with F4 or with toolbar button at the actual cursor position.
The trace recording will be started with "Enable Trace". It will be written into the file "temptrace.log". This file stores the instruction
trace in binary format, the variable-trace in ASCII format. To view the recorded trace use the trace view window. This 
window is opened from menu with "View-Trace", from toolbar or alternatively from command line with "w.t". 
The length of the trace is limited to 1000 entries. If this value is reached the file will be written from the front again.
The trace window is only available if "Enable Trace" is set to off.
Stimulations file:
New since version 3.01 is the possibility to set a so called stimulation point. That means, while your program is running, each
time when this point will be reached a line from a script will be read and entered in the command line.
The scrip file is a normal text file which can contain all valid commands (in German)  for the command line. Multiple orders must be
separated with a comma. Meaning of such stimulation file is for example to simulate changes on port pins.
An example could be this:  
$m.d 0x20 0x05 , s.d 0x22 0xAA   set DATA memory at address 0x20 to 5 and at addresses 0x22 to 0xAA
$m.x 0x1000 0xAB   set XDATA-cell 0x1000 to 0xAB
w.m.l 0x9F0 /x   opens a memory window
b.s 0x3400   set a break point to address 0x3400
At the file end it starts again from the begin. A stimulation point can be set via the command line or from the toolbar.
The  simulation point and also the path of the stimulation file will be stored in the workspace.

The program cannot simulate the peripheral hardware of the controller. That means, you can set all register to start a timer
but after all no timer is started. The problem is, if you want to simulate such hardware dependencies, you must realise the
synchronisation to the processor clock. But this is not so easy, well then no hardware simulation. Nevertheless actions which
will be initiated by interrupts should be testable. For that serves the interrupt window. It allows to configure and generate the
five standard interrupts of the 8051. The serial interrupt will be discriminate between receive and transmit interrupt.
To  cause a interrupt, it must be enabled and the global enable bit must be set. It's possible to generate more than one interrupt
at once. The simulator should process each interrupt as configured via the priority. A present but not processed interrupt is
recognizable by the activated interrupt button. The simulator reset it automatically after its acceptance. ( Exception the Flags
RI,TI of the serial interrupt which must be cleared by the program self ). The window is updated after each stop, step etc. 
Changes of interrupt settings will be visible first after the next stop !


The stack window shows the depth of the procedure calls. At each call or also at interrupt acceptance a entry ahead of the list
is inserted. It shows the actual procedure and the address from where it is called. With double click you can switch the view 
to the call address. This may be problematically if the stack pointer or its content will be changed by the program. The stack
window tries to follow such actions, but its not so difficult to confuse it. You must be careful with the displayed content if you 
use POP and PUSH in the program. On overrun of the stack pointer a message is generated but you can continue the program.

Often programs for serial communication are to develop. As result of this I made a  restricted exception concerning the hardware
simulation. But only qualitative. The time relations are not correctly realised. The right initialisation of the timer isn't checked.
The terminal window will be opened from menu with "View-Terminal" or from command line with "w.c". The terminal window 
simulate a receive interrupt if a character is entered. In the other direction it simulate a transmit interrupt if the register S0BUF
is changed. Only a transmission of 8-bit width is possible. The start of the window slow down the simulation something because
it use its own thread. It should be opened only if you really need it. The option "local echo" cause that every character is displayed 
directly in the window. If the option is off, all characters will be written into S0BUF and, if enabled (ES=1 in IE0 + REN=1 in S0CON),
the serial interrupt will be set (RI=1 in S0CON).
If you want to display binary characters the option "binary string" must be on. A received string has than the format:
 00 02 09 41 FF ...etc. The input must be done in the same form. All characters, except backspace, which are no hexadecimal 
numbers are ignored.
 You cannot send strings with copy-paste to the terminal window. I don't know how I should handle this. The terminal 
window need to wait that the interrupt has processed a character. But this reflect not the real behavior and you be in danger to 
test something to "good".

Notes for loading SDC51/ASxxxLink - files

In contrast to other compiler/linker SDC51/ASxxxLink produce as output not only one objectfile. The symbol type informations
are stored separately for each HLL-module in a symbol-type-definition file ( .cdb) and the addresses and line informations in the
mapfile ( .map).
So if you want to debug on C-level, you need all the following files in the same directory:

To load the project open the .ihx-file. JSIM knows than that you want to load a SDC51 project and searches
as next for
the map file ( .map ) with the same name as the hexfile(!) and than for the .cdb- and source files.
The sources must be compiled with the option --debug.
Each source must be build separately and at last step they will be linked all together
Here is a short example:

..\bin\sdcc -c --debug mod1.c
..\bin\asx8051 -olj aprog.a51
..\bin\sdcc -c --debug main.c
..\bin\sdcc -Wl-mjkC:\sdcc\sdcc51lib main.rel aprog.rel mod1.rel

There are some restrictions which are caused by the SDC-compiler:

It may be a good idea to use JFE as program editor. If you use the compiler filter "MS C/C++" you can compile with SDCC
and jump directly to the errors and warnings!