{"id":22,"date":"2019-11-06T08:03:00","date_gmt":"2019-11-06T13:03:00","guid":{"rendered":"https:\/\/www.vociferousvoid.org\/?p=22"},"modified":"2023-07-01T21:40:02","modified_gmt":"2023-07-02T01:40:02","slug":"risc-v-bare-metal-programming-chapter-2-opcodes-assemble","status":"publish","type":"post","link":"https:\/\/www.vociferousvoid.org\/index.php\/2019\/11\/06\/risc-v-bare-metal-programming-chapter-2-opcodes-assemble\/","title":{"rendered":"RISC-V Bare Metal Programming Chapter 2: OpCodes Assemble!"},"content":{"rendered":"\n<p>The <a href=\"https:\/\/www.vociferousvoid.org\/index.php\/2019\/11\/01\/risc-v-bare-metal-programming-chapter-1-the-setup\/\">previous chapter<\/a> of this tutorial went over the steps required to setup a RISC-V development environment to create a program that runs on a bare metal VirtIO board using QEMU. Even though the example program \u2013 which calculates the sum two integers \u2013 was written in RISC-V assembly, no prior knowledge was required to follow along. This chapter will dive into the details of RISC-V assembly language as well as expand on what exactly is happening at each step of the development. The topics covered in this chapter will include an overview of the RISC-V architecture, its assembly instructions, pseudo-instructions, and directives.<\/p>\n\n\n\n<p>The following listing illustrates the assembly code of the <code>add.s<\/code> program from the previous chapter:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>1:         .text\n2:         .global _start\n3: _start:\n4:         li      a0, 5\n5:         li      a1, 4\n6:         add     a0, a0, a1\n7: stop:   j       stop<\/code><\/pre>\n\n\n\n<p>The code was changed a little to use a different set of registers. This program when assembled results in an object file which can then be linked to create the file that will be loaded onto the board. In this scenario, only the one object file is used, however, more complex programs may required more than one object file. The linker&#8217;s job is to put all of these files together into a single executable program.<\/p>\n\n\n\n<p>This <code>add.s<\/code> program is composed of one instruction (<a href=\"https:\/\/www.vociferousvoid.org\/main\/riscv_bare_metal_chapter2#coderef-add\">6<\/a>), three pseudo-instructions (<a href=\"https:\/\/www.vociferousvoid.org\/main\/riscv_bare_metal_chapter2#coderef-load5\">4<\/a> <a href=\"https:\/\/www.vociferousvoid.org\/main\/riscv_bare_metal_chapter2#coderef-load4\">5<\/a>, and <a href=\"https:\/\/www.vociferousvoid.org\/main\/riscv_bare_metal_chapter2#coderef-loop\">7<\/a>), and two directives (<a href=\"https:\/\/www.vociferousvoid.org\/main\/riscv_bare_metal_chapter2#coderef-section\">1<\/a>, <a href=\"https:\/\/www.vociferousvoid.org\/main\/riscv_bare_metal_chapter2#coderef-start_sym\">2<\/a>). Moreover, the instructions and pseudo-instructions have operands comprised of either registers or immediates. Understanding each of these entities will help when creating more complex programs in RISC-V assembly.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\" id=\"org71d0bd6\">Registers<\/h1>\n\n\n\n<p>RISC-V systems will have a base set of 32 registers <code>x0-x31<\/code>. The <code>x0<\/code> register is read-only with a value fixed to zero. The rest will have varying content. The application binary interface (ABI) prescribes conventions for the name and usage of the various registers. These are listed in the following table:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Register(s)<\/th><th>ABI Name(s)<\/th><th>Description<\/th><th>Saved by<\/th><\/tr><\/thead><tbody><tr><td>x0<\/td><td>zero<\/td><td>Hard-wired zero<\/td><td>N\/A<\/td><\/tr><tr><td>x1<\/td><td>ra<\/td><td>Function return address<\/td><td>Caller<\/td><\/tr><tr><td>x2<\/td><td>sp<\/td><td>Stack pointer<\/td><td>Callee<\/td><\/tr><tr><td>x3<\/td><td>gp<\/td><td>Global pointer<\/td><td>N\/A<\/td><\/tr><tr><td>x4<\/td><td>tp<\/td><td>Thread pointer<\/td><td>N\/A<\/td><\/tr><tr><td>x5<\/td><td>t0<\/td><td>Temporary\/alternate link register<\/td><td>Caller<\/td><\/tr><tr><td>x6-x7<\/td><td>t1-t2<\/td><td>Temporary values<\/td><td>Caller<\/td><\/tr><tr><td>x8<\/td><td>s0\/fp<\/td><td>Saved register\/Frame pointer<\/td><td>Callee<\/td><\/tr><tr><td>x9<\/td><td>s1<\/td><td>Saved register<\/td><td>Callee<\/td><\/tr><tr><td>x10-x11<\/td><td>a0-a1<\/td><td>Function arguments\/Return values<\/td><td>Caller<\/td><\/tr><tr><td>x12-x17<\/td><td>a2-a7<\/td><td>Function arguments<\/td><td>Caller<\/td><\/tr><tr><td>x18-x27<\/td><td>s2-s11<\/td><td>Saved registers<\/td><td>Callee<\/td><\/tr><tr><td>x28-x31<\/td><td>t3-t6<\/td><td>Temporary values<\/td><td>Caller<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Registers can be referred by their ABI names or their actual names in assembly programs; the two are interchangeable.<\/p>\n\n\n\n<p>When a function is invoked, it may modify the values of some of these registers. For this reason it is advisable to save the contents of those registers in memory in order to be able to restore them when the function completes. The ABI convention prescribes which party in a function call (the caller or the callee) is responsible for saving these values. This convention is described in the &#8220;Saved by&#8221; column of the table.<\/p>\n\n\n\n<p>If a register is to be saved by the caller, its value should be stored in a frame of the stack, that was allocated for that purpose, prior to calling the function. This ensures that the values can be restored when the function returns. In general, it is a good idea to save all of the registers if the caller does not know which registers may be modified by the callee.<\/p>\n\n\n\n<p>Registers to be saved by the callee only need to be saved to memory if the function uses those registers. Functions must not leave a trace, the state of the machine must be the same as it was prior to the function being invoked (with the exception of the desired function result).<\/p>\n\n\n\n<p>A function implementation defined in RISC-V assembly should use the following prologue before doing any of its work:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>function_label:\n        addi    sp, sp, -framesize      # The stack grows downward\n        sd      ra,framesize-8(sp)      # Save the return address\n        # Save registers owned by the callee as needed to memory.<\/code><\/pre>\n\n\n\n<p>This will ensure that the function can return to the point where it was called, and that any register state will be saved.<\/p>\n\n\n\n<p>Before a function returns, the saved register values must be restored. This is achieved by the following epilogue which should end a function call.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># restore registers from the stack if needed\n        ld      ra, framesize-8(sp)     # Restore the return address register\n        addi    sp, sp, framesize       # Pop the stack\n        ret     # return to the caller<\/code><\/pre>\n\n\n\n<p>This will restore the saved registers, set the return address and de-allocate the stack frame that was used to save this information.<\/p>\n\n\n\n<p>The <code>add.s<\/code> program can be enhanced to use the function prologue and epliogue to define a function that calculates the sum its arguments in registers <code>a0<\/code> and <code>a1<\/code>. This new program is illustrated in the listing that follows:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>        .text\n        .align 2\n        .global sum\nsum:\n        addi    sp, sp, -32     # Stack frames must be 16-bit aligned\n        sd      ra, 24(sp)      # Save the return address\n        add     a0, a0, a1      # Add the function operands\n        ld      ra, 24(sp)      # restore return address\n        addi    sp, sp, 32      # De-allocate the stack frame\n        ret<\/code><\/pre>\n\n\n\n<p>The <code>sum <\/code>function can be called by name from a different assembler program. Create a <code>main.s<\/code> program with the following content:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>        .text\n        .align 2\n        .global _start\n_start:\n        li      a0, 5\n        li      a1, 4\n        call    sum\nstop:   j       stop<\/code><\/pre>\n\n\n\n<p>This will load the values 5 and 4 into the registers used for arguments to the the sum function and call it. This can be assembled and linked as follows:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ riscv64-unknown-elf-as -o add.o add.s\n$ riscv64-unknown-elf-as -o main.o main.s\n$ riscv64-unknown-elf-ld -Ttext=0x80000000 -o sum.elf main.o add.o<\/code><\/pre>\n\n\n\n<p>If this program is assembled, linked, and run in QEMU, it will call the <code>sum<\/code> function to calculate the sum of the operands. This can be verified by inspecting the value of register <code>a0<\/code> which should be 9.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"has-small-font-size\"><strong>NOTE<\/strong>: The order in which the object files are supplied to the linker is important. If the <code>add.o<\/code> file is supplied first, the program will not run because its content will be located at the reset address.<\/p>\n<\/blockquote>\n\n\n\n<h1 class=\"wp-block-heading\" id=\"org7a6a704\">Instructions<\/h1>\n\n\n\n<p>Instructions are mnemonics that map directly to machine codes. For example the <code>add<\/code> instruction in the <code>sum<\/code> function corresponds with the op-code <code>0x33<\/code> (or <code>b0110011<\/code>).<\/p>\n\n\n\n<p>When the <code>add<\/code> instruction is combined with its operands, the result is a single machine instruction. In RISC-V, all machine instructions are 32-bits long (unless you&#8217;re using the compressed extension, but for now we&#8217;ll just deal with 32-bit operations).<\/p>\n\n\n\n<p>The <code>add<\/code> instruction is what&#8217;s known as an R-type instruction because its operands are all registers. This type of instruction has the following form:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>BITS<\/th><th>31:25<\/th><th>24:20<\/th><th>19:15<\/th><th>14:12<\/th><th>11:7<\/th><th>6:0<\/th><\/tr><\/thead><tbody><tr><td>R-Type<\/td><td>func7<\/td><td>rs2<\/td><td>rs1<\/td><td>func3<\/td><td>rd<\/td><td>opcode<\/td><\/tr><\/tbody><\/table><figcaption class=\"wp-element-caption\">Structure of the R-Type op-codes<\/figcaption><\/figure>\n\n\n\n<p>In this table, the operation&#8217;s function is a combination of <strong>func7<\/strong> and <strong>func3<\/strong>. For the <code>add<\/code> instruction, this is <code>b0000000<\/code> and <code>b000<\/code>. The <strong>rs2<\/strong> and <strong>rs1<\/strong> field are the source registers whose value will be added. The <strong>rd<\/strong> field will be the destination register for the result. Notice that the register fields are 5-bits wide. This allows the instruction to reference any of the 32 base registers (i.e. 2<sup>5<\/sup> registers). The <code>add<\/code> instruction from the previous example will be constructed as follows:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>func7<\/strong>: <code>b0000000<\/code><\/li>\n\n\n\n<li><strong>rs2<\/strong>: <code>b01011<\/code> (for <code>x11<\/code> which is <code>a1<\/code>)<\/li>\n\n\n\n<li><strong>rs1<\/strong>: <code>b01010<\/code> (for <code>x10<\/code> which is <code>a0<\/code>)<\/li>\n\n\n\n<li><strong>func3<\/strong>: <code>b000<\/code><\/li>\n\n\n\n<li><strong>rd<\/strong>: <code>b01010<\/code> (for <code>x10<\/code>) opcode <code>b0110011<\/code><\/li>\n<\/ul>\n\n\n\n<p>Putting it all together, we get <code>b00000000101101010000010100110011<\/code>, or <code>0x00B50533<\/code>. We can confirm this by disassembling the object file that was created:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ riscv64-unknown-elf-objdump -d add.o\n\nsum.o:     file format elf64-littleriscv\n\n\nDisassembly of section .text:\n\n0000000000000000 &lt;sum&gt;:\n   0:   fe010113                addi    sp,sp,-32\n   4:   00113c23                sd      ra,24(sp)\n   8:   00b50533                add     a0,a0,a1\n   c:   01813083                ld      ra,24(sp)\n  10:   02010113                addi    sp,sp,32\n  14:   00008067                ret<\/code><\/pre>\n\n\n\n<p>The <code>add<\/code> instruction in the <code>sum<\/code> function is at offset 8 of the object file, the machine instruction is <code>00b50533<\/code> which is the value that we had calculated for the instruction.<\/p>\n\n\n\n<p>In addition to R-Type instructions, there are also I-Type instructions that operate on immediates (literal values), S-Type for storing to memory, U-Type for loading values from memory, B-Type for branching, and J-Type for jumps (e.g. function calls). The following table describes the layout of each of these instruction types:<\/p>\n\n\n\n<figure class=\"wp-block-table alignwide\"><table><thead><tr><th>BITS<\/th><th>31:25<\/th><th>24:20<\/th><th>19:15<\/th><th>14:12<\/th><th>11:7<\/th><th>6:0<\/th><\/tr><\/thead><tbody><tr><td>R-Type<\/td><td>func7<\/td><td>rs2<\/td><td>rs1<\/td><td>func3<\/td><td>rd<\/td><td>opcode<\/td><\/tr><tr><td>I-Type<\/td><td>imm[11:5]<\/td><td>imm[4:0]<\/td><td>rs1<\/td><td>func3<\/td><td>rd<\/td><td>opcode<\/td><\/tr><tr><td>S-Type<\/td><td>imm[11:5]<\/td><td>rs2<\/td><td>rs1<\/td><td>func3<\/td><td>imm[4:0]<\/td><td>opcode<\/td><\/tr><tr><td>U-Type<\/td><td>imm[31:25]<\/td><td>imm[24:20]<\/td><td>imm[19:15]<\/td><td>imm[14:12]<\/td><td>rd<\/td><td>opcode<\/td><\/tr><tr><td>B-Type<\/td><td>imm[12,10:5]<\/td><td>rs2<\/td><td>rs1<\/td><td>func3<\/td><td>imm[4:1,11]<\/td><td>opcode<\/td><\/tr><tr><td>J-Type<\/td><td>imm[20,10:5]<\/td><td>imm[4:1,11]<\/td><td>imm[19:15]<\/td><td>imm[14:12]<\/td><td>rd<\/td><td>opcode<\/td><\/tr><\/tbody><\/table><figcaption class=\"wp-element-caption\">Format of the various RISC-V instruction types<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"orga251f7e\">Pseudo-Instructions<\/h2>\n\n\n\n<p>Unlike instructions, pseudo-instructions do not map directly to op-codes. Typically these represent idioms to make the programmer&#8217;s life a little easier.<\/p>\n\n\n\n<p>For example, the <code>li<\/code> in the <code>sum<\/code> program is an example of a pseudo-instruction. As explained in the previous chapter, this pseudo-instruction maps to an <code>addi<\/code> I-Type instruction which adds the value of <code>x0<\/code> to the immediate value and stores the result in the destination register. Pseudo-instructions provide convenient mnemonics for programming without adding additional op-codes.<\/p>\n\n\n\n<p>Pseudo-instructions may also translate to more than one assembler instruction. For example, the <code>call<\/code> pseudo-instruction will be<br>translated into a sequence of three instructions: <code>auipc<\/code>, <code>addi<\/code>, and<strong> <\/strong><code>jal<\/code>.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Directives<\/h1>\n\n\n\n<p>Directives are commands for the assembler rather than instructions that it will translate into machine code. Directives can be used to tell the assembler where to place code and data in the resulting object file, or to setup the memory of the target system. The previous example used assembler directives to export global symbols, to set the alignment for instructions, and to ensure that the code is assembled into the &#8220;.text&#8221; section of the object file.<\/p>\n\n\n\n<p>To understand the purpose of the assembler directives, it is important to understand how assembled code is linked together. The assembler produces object files that are combined to produce an Executable and Linkable Format (ELF) file. This file will be segmented into different sections:<code>.text<\/code> CPU instructions (the executable code). <code>.rodata<\/code> Read-only data. <code>.data<\/code> Global, mutable, initialized data. <code>.bss<\/code> Global, mutable, un-initialized data.<\/p>\n\n\n\n<p>Up to now, only the text section has been used. The location where the code is loaded was specified using the &#8220;-T&#8221; option when invoking the linker. If multiple object files are passed to the linker, their text sections merged into a single contiguous section.<\/p>\n\n\n\n<p>Code and data will have different run-time requirements. Code is generally read-only where as data my required read-write permissions. Therefore it is advantageous that code and data are not interleaved. To ensure this, the locations of text and data sections of the program should not overlap.<\/p>\n\n\n\n<p>To avoid having to define the position of each section at the command line, the linker allows the memory layout to be defined using a linker script:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>OUTPUT_ARCH( \"riscv\" )\nSECTIONS {\n\t. = 0x80000000;\n\t.text : {\n\t\tPROVIDE(_text_start = .);\n\t\t.*(.text.init)\n\t\tmain.o (.text)\n\t\t.*(.text .text.*)\n\t\tPROVIDE(_text_end = .);\n\t}\n\tPROVIDER(_global_pointer = ,);\n\t.rodata : {\n\t\tPROVIDE(_rodata_start = .);\n\t\t.*(.rodata .rodata.*)\n\t\tPROVIDE(_rodata_end = .);\n\t}\n\t.data : {\n\t\t. = ALIGN(4096);\n\t\tPROVIDE(_data_start = .);\n\t\t.*(.sdata .sdata.*) *(.data .data.*)\n\t\tPROVIDE(_data_end = .);\n\t}\n\t.bss : {\n\t\tPROVIDE(_bss_start = .);\n\t\t.*(.sbss .sbss.*) *(.bss .bss.*)\n\t\tPROVIDE(_bss_end = .);\n\t}\n\tPROVIDE(_stack_start = _bss_end);\n\tPROVIDE(_stack_end = _stack_start + 0x8000);\n}<\/code><\/pre>\n\n\n\n<p>The <code>SECTIONS<\/code> keyword is used to specify how the various sections are layed out in the file. In the linker script shown previously, the <code>.text<\/code>, <code>.rodata<\/code>, <code>.data<\/code>, and <code>.bss<\/code> sections are defined.<\/p>\n\n\n\n<p>The .text section will include all code that follows a <code>.section .text.init<\/code> or <code>.text<\/code> assembler directive. The <code>sum.s<\/code> and <code>main.s<\/code> will therefore both be included in this section. On line <a href=\"https:\/\/www.vociferousvoid.org\/main\/riscv_bare_metal_chapter2#coderef-main\">7<\/a> of the linker script, the text section of the <code>main.o<\/code> object file is included explicitly. This will ensure that the main program appears in the linked program before the <code>sum<\/code> function does (which is included by the wildcard on the next line).<\/p>\n\n\n\n<p>The <code>PROVIDE<\/code> keyword is used to define a symbol at the address of the definition. The start and end of each of the sections will be provided by the linker. Moreover, the start and end of the stack memory area can be declared in this way. In a later chapter, these symbols will be used to setup the stack pointer.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Putting it All Together<\/h1>\n\n\n\n<p>The program can now be assembled and linked using the following sequence of commands:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ riscv64-unknown-elf-as -o sum.o sum.s\n$ riscv64-unknown-elf-as -o main.o main.s\n$ riscv64-unknown-elf-ld -T linker.lds -o sum.elf main.o sum.o<\/code><\/pre>\n\n\n\n<p>This will produce an ELF file called <code>sum.elf<\/code>. By inspecting the <code>sum.elf<\/code> file, we can see that the <code>_start<\/code> symbol shows up before the <code>sum<\/code> function:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ riscv64-unknown-elf-objdump -d sum.elf \n\nsum.elf:     file format elf64-littleriscv\n\n\nDisassembly of section .text:\n\n0000000080000000 &lt;_start&gt;:\n    80000000:\t00500513          \tli\ta0,5\n    80000004:\t00400593          \tli\ta1,4\n    80000008:\t00009117          \tauipc\tsp,0x9\n    8000000c:\tff810113          \taddi\tsp,sp,-8 # 80009000 &lt;_stack_end&gt;\n    80000010:\t008000ef          \tjal\tra,80000018 &lt;sum&gt;\n\n0000000080000014 &lt;stop&gt;:\n    80000014:\t0000006f          \tj\t80000014 &lt;stop&gt;\n\n0000000080000018 &lt;sum&gt;:\n    80000018:\tfe010113          \taddi\tsp,sp,-32\n    8000001c:\t00113c23          \tsd\tra,24(sp)\n    80000020:\t00b50533          \tadd\ta0,a0,a1\n    80000024:\t01813083          \tld\tra,24(sp)\n    80000028:\t02010113          \taddi\tsp,sp,32\n    8000002c:\t00008067          \tret<\/code><\/pre>\n\n\n\n<p>The disassembled <code>sum.elf<\/code> also shows that the <code>call<\/code> pseudo-instruction was translated to the following sequence of instructions:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>auipc   sp,0x9\naddi    sp,sp,-8\njal     ra,80000018<\/code><\/pre>\n\n\n\n<p>This program can be run in QEMU just as before and the result should be the same as previous runs.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\" id=\"orgd0dd85d\">Conclusion<\/h1>\n\n\n\n<p>This chapter of the Bare Metal RISC-V tutorial covered the assembly language in a little more details. Assembly programs are made up of directives, pseudo-instructions, and instructions. Directives provide guidance to the assembler on how to organize the assembled code. Pseudo-instructions provide useful mnemonics that are mapped to one or more primitive assembler instructions. Instructions are translated into binary machine instructions which direct the execution flow of the processor. In future chapters, this information will be utilised to make the RISC-V processors do more interesting things.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The previous chapter of this tutorial went over the steps required to setup a RISC-V development environment to create a program that runs on a bare metal VirtIO board using QEMU. Even though the example program \u2013 which calculates the sum two integers \u2013 was written in RISC-V assembly, no prior knowledge was required to [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[4],"tags":[],"class_list":["post-22","post","type-post","status-publish","format-standard","hentry","category-risc-v"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.vociferousvoid.org\/index.php\/wp-json\/wp\/v2\/posts\/22","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.vociferousvoid.org\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.vociferousvoid.org\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.vociferousvoid.org\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.vociferousvoid.org\/index.php\/wp-json\/wp\/v2\/comments?post=22"}],"version-history":[{"count":3,"href":"https:\/\/www.vociferousvoid.org\/index.php\/wp-json\/wp\/v2\/posts\/22\/revisions"}],"predecessor-version":[{"id":29,"href":"https:\/\/www.vociferousvoid.org\/index.php\/wp-json\/wp\/v2\/posts\/22\/revisions\/29"}],"wp:attachment":[{"href":"https:\/\/www.vociferousvoid.org\/index.php\/wp-json\/wp\/v2\/media?parent=22"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.vociferousvoid.org\/index.php\/wp-json\/wp\/v2\/categories?post=22"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.vociferousvoid.org\/index.php\/wp-json\/wp\/v2\/tags?post=22"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}