Old question, but this may work for those who are still looking. I checked this out on my Ampro Little Board running 1980 M80/L80 on CP/M 2.2.
You can use the ASEG (absolute) directive in your starting .MAC file, specify 0D000H as the org, and then reference external modules. As long as those external modules don't include DSEG or PSEG directives you should be able to link them all together with 0D000H as the starting address. E.g.
; TEST.MAC
ASEG
ORG 0D000H
public tstart
tstart:
...
call myfoo## ; call routine myfoo in external module foo.rel
...
end tstart
Assemble it:
M80 TEST,=TEST
Link it with foo.rel and use /X on the output to produce a .HEX file (TEST.HEX):
L80 TEST,FOO,TEST/N/X/E
If you examine the resulting .HEX file you should see the starting address is 0D000H.
BTW: If you don't use /X option then L80 with /N/E will make a .COM with with all the code linked using an offset of 0D000H unless you also include a .phase directive. E.g.:
; TEST.MAC
ASEG
ORG 100H
.phase 0D000H
public tstart
tstart:
...
call myfoo## ; call routine myfoo in external module foo.rel
...
end tstart
Link to make a .COM instead of a .HEX:
L80 TEST,FOO,TEST/N/E <== note no '/X'
You can't run it, but you can consider the .COM file is really a .BIN padded to the nearest 128 byte boundary (assuming that your CP/M is using the typical approach of allocating 128 byte blocks). You can confirm the result by doing a DUMP of the .COM file. If the code was very short it may also include leftover pieces of L80 loader code that wasn't overwritten by your code.
Note you can use also the ASEG approach with org 0100H to make a regular CP/M .COM. In that case you don't need to use .phase assuming the start of your code is at 100H.