[otbn] Properly generate multi-section output in otbn-rig

The code was trying to use the .offset assembler directive to tell it
where to put things, but this turns out not to work at all. So we now
generate a linker script as well as the assembly code.

The command line is pretty horrible, but will be tidied up
soon (splitting up "generate me some snippets" and "turn them into
assembly"). For now, you can run it as

    ./hw/ip/otbn/util/otbn-rig \
      --asm-output gen.S \
      --ld-output gen.ld \
      -o gen.json

The resulting gen.S looks something like this:

    /* Section 0 (addresses [0x0000..0x0007]) */
    .section .text.sec0000
    addi          x30, x0, 1269
    jal           x6, 3320

    /* Section 1 (addresses [0x02d4..0x0333]) */
    .section .text.sec0001
    lui           x29, 29447
    srl           x18, x30, x30
    slli          x17, x24, 5
    ori           x16, x17, -1155
    or            x3, x16, x29
    sub           x25, x18, x29
    or            x6, x6, x0
    lui           x21, 273100
    and           x25, x24, x24
    ...

and gen.ld looks something like this:

    SECTIONS
    {
        /* Section 0 (addresses [0x0000..0x0007]) */
        .text.sec0000 0x0 : AT(0x100000)
        {
            *(.text.sec0000)
        }

        /* Section 1 (addresses [0x02d4..0x0333]) */
        .text.sec0001 0x2d4 : AT(0x1002d4)
        {
            *(.text.sec0001)
        }
    ...

Signed-off-by: Rupert Swarbrick <rswarbrick@lowrisc.org>
diff --git a/hw/ip/otbn/util/otbn-rig b/hw/ip/otbn/util/otbn-rig
index 0ea366d..5c52d87 100755
--- a/hw/ip/otbn/util/otbn-rig
+++ b/hw/ip/otbn/util/otbn-rig
@@ -27,6 +27,8 @@
                         help='Path for JSON output of generated snippets')
     parser.add_argument('--asm-output',
                         help='Optional path for generated program')
+    parser.add_argument('--ld-output',
+                        help='Optional path for generated linker script')
     parser.add_argument('--seed', type=int, default=0,
                         help='Random seed. Defaults to 0.')
     parser.add_argument('--size', type=int, default=100,
@@ -69,6 +71,16 @@
                              .format(args.asm_output, err))
             return 1
 
+    # If a linker script was requested, dump that here
+    if args.ld_output is not None:
+        try:
+            with open(args.ld_output, 'w') as out_file:
+                program.dump_linker_script(out_file)
+        except OSError as err:
+            sys.stderr.write('Failed to open ld script output file {!r}: {}.'
+                             .format(args.ld_output, err))
+            return 1
+
     return 0
 
 
diff --git a/hw/ip/otbn/util/rig/program.py b/hw/ip/otbn/util/rig/program.py
index 1771228..15db1b4 100644
--- a/hw/ip/otbn/util/rig/program.py
+++ b/hw/ip/otbn/util/rig/program.py
@@ -65,8 +65,9 @@
     # instructions for the section.
     _SecData = Tuple[int, int, List[ProgInsn]]
 
-    def __init__(self, imem_size: int) -> None:
+    def __init__(self, imem_lma: int, imem_size: int) -> None:
         assert imem_size & 3 == 0
+        self.imem_lma = imem_lma
         self.imem_size = imem_size
 
         # A map from base address (VMA) to a list of instructions. Each
@@ -149,15 +150,22 @@
         assert self._cur_section is not None
         self._cur_section[1].add_insns(insns)
 
+    @staticmethod
+    def _get_section_comment(idx: int,
+                             addr: int,
+                             insns: List[ProgInsn]) -> str:
+        return ('/* Section {} (addresses [{:#06x}..{:#06x}]) */'
+                .format(idx, addr, addr + 4 * len(insns) - 1))
+
     def dump_asm(self, out_file: TextIO) -> None:
         '''Write an assembly representation of the program to out_file'''
         # Close any existing section, so that we can iterate over all the
         # instructions by iterating over self._sections.
         self.close_section()
         for idx, (addr, insns) in enumerate(sorted(self._sections.items())):
-            out_file.write('{}/* Section {} ({} instructions) */\n'
-                           .format('\n' if idx else '', idx, len(insns)))
-            out_file.write('.offset {:#x}\n'.format(addr))
+            comment = Program._get_section_comment(idx, addr, insns)
+            out_file.write('{}{}\n'.format('\n' if idx else '', comment))
+            out_file.write('.section .text.sec{:04}\n'.format(idx))
             for pi in insns:
                 insn = pi.insn
                 # We should never try to generate an instruction without syntax
@@ -182,6 +190,27 @@
 
                 out_file.write('{:14}{}\n'.format(mnem, rendered_ops))
 
+    def dump_linker_script(self, out_file: TextIO) -> None:
+        '''Write a linker script to link the program
+
+        This lays out the sections generated in dump_asm().
+
+        '''
+        self.close_section()
+        out_file.write('SECTIONS\n'
+                       '{\n')
+        for idx, (addr, insns) in enumerate(sorted(self._sections.items())):
+            comment = Program._get_section_comment(idx, addr, insns)
+            out_file.write('{}    {}\n'.format('\n' if idx else '', comment))
+            sec_name = '.text.sec{:04}'.format(idx)
+            lma = addr + self.imem_lma
+            out_file.write('    {} {:#x} : AT({:#x})\n'
+                           '    {{\n'
+                           '        *({})\n'
+                           '    }}\n'
+                           .format(sec_name, addr, lma, sec_name))
+        out_file.write('}\n')
+
     def pick_branch_targets(self,
                             min_len: int,
                             count: int,
diff --git a/hw/ip/otbn/util/rig/rig.py b/hw/ip/otbn/util/rig/rig.py
index 684635b..aca1edf 100644
--- a/hw/ip/otbn/util/rig/rig.py
+++ b/hw/ip/otbn/util/rig/rig.py
@@ -31,13 +31,13 @@
     # at address 0: a strict Harvard architecture. (mems[x][0] is the LMA
     # for memory x, not the VMA)
     mems = get_memory_layout()
-    imem_size = mems['IMEM'][1]
+    imem_lma, imem_size = mems['IMEM']
     dmem_size = mems['DMEM'][1]
 
     assert start_addr <= imem_size - 4
     assert start_addr & 3 == 0
 
-    program = Program(imem_size)
+    program = Program(imem_lma, imem_size)
     model = Model(dmem_size, start_addr)
 
     generators = SnippetGens(insns_file)