asm

package
v0.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 24, 2025 License: MPL-2.0 Imports: 11 Imported by: 1

README

ulp-asm

ulp-asm is an assembler for the ESP32 ULP coprocessor. Note that this converts assembly directly into the final binary.

ulp-asm currently only supports one file.

ulp-asm currently only supports the original ESP32.

Directives

  • .global symbol
  • .int
  • .boot, code here will be placed at the start of the .text section
  • .boot.data, code here will be placed at the start of the .data section
  • .text
  • .data
  • .bss

Instructions

  • add
  • sub
  • and
  • or
  • move
  • lsh
  • rsh
  • stage_rst
  • stage_inc
  • stage_dec
  • st
  • ld
  • jump
  • jumpr
  • jumps
  • halt
  • wake
  • sleep
  • wait
  • adc
  • i2c_rd
  • i2c_wr
  • reg_rd
  • reg_wr

Comments

The following comment types are supported:

// I am a comment until end of line
# I am also a comment until end of line
/* I am an inline comment */

Sections

The ULP binary contains a header with information about the .text, .data, and .bss sections. These will be referred to as .header.text, .header.data, and .header.bss respectively in this document.

This assembler has the .boot and .text directives. These will be added to the .header.text section in order. The label "__boot_start" is put at the start of the .boot section, "__boot_end" at the end. The label "__text_start" is put at the start of the .text section, "__text_end" at the end.

This assembler has the .boot.data and .data directives. These will be added to the .header.data section in order. The label "__boot_data_start" is put at the start of the .boot.data section, "__boot_data_end" at the end. The label "__data_start" is put at the start of the .data section, "__data_end" at the end.

This assembler uses the .bss directive to put data in the .header.bss section. The label "__bss_start" is placed at the start, "__bss_end" at the end.

This assembler allocates the remainder of the reserved space for a stack at the end of the .header.bss section. The label "__stack_start" is placed at the start, "__stack_end" at the end.

Common code reduction

Quite often there are common series of instructions that can be jumped to in order to save space. Given the following subroutines:

  func0:
ld r0, r0, data0
mv r1, 0xFFFF
st r1, r0, 0
jump r2

  func1:
ld r0, r0, data1
mv r1, 0xFFFF
st r1, r0, 0
jump r2

We can reduce the code to:

  func0:
ld r0, r0, data0
  common_series:
mv r1, 0xFFFF
st r1, r0, 0
jump r2

  func1:
ld r0, r0, data1
jump common_series

Logically this can be done if:

  1. We do not use relative arguments (so no .).
  2. All instructions are exactly identical.
  3. The series of instructions ends in a definite jump.
  4. The series is only instructions. No labels, no data.

This is unsafe if code outside of that series attempts to access within the series:

  unsafe:
jump func1+2

This cannot be statically checked. As a result, the option to reduce common instructions is behind the --reduce flag.

While this optimization is logically correct, this may interfere with time-sensitive code. To circumvent this, you can add . into an argument before a jump to prevent the optimization:

  critical:
add r0, r0, 1
add r1, r1, 2
// other time sensitive code
// ...
// same as "add r0, r0, 10"
add r0, r0, 10 + . - . // prevents reducing
jump r2

Differences

There are several differences between ulp-asm and esp32ulp-elf-as.

Number labels

esp32ulp-elf-as can use number labels such as the following:

  0:
add r0, r0, 10
jump 0b, ov

Where the jump will go back to the nearest 0 label on overload.

ulp-asm does not currently support this.

Case Sensitivity

esp32ulp-elf-as is case insensitive: it allows all instructions to be upper case or lower case.

ulp-asm is case sensitive: all instructions are lower case.

Math with labels

Note that this applies to any expression.

esp32ulp-elf-as divides the final output of any expression involving a label by 4. For example, the expression entry+3 will compile to the same as the expression entry because the extra addition is truncated. Additionally, it will only allow one label per expression.

ulp-asm does not divide the final output, so entry and entry+3 will compile to different values.

ld instruction

esp32ulp-elf-as divides the offset by 4, ulp-asm does not.

esp32ulp-elf-as:

ld r0, r0, 0
ld r1, r1, 4
ld r2, r2, 5
ld r3, r3, 20

equivalent ulp-asm:

ld r0, r0, 0
ld r1, r1, 1
ld r2, r2, 1
ld r3, r3, 5

st instruction

esp32ulp-elf-as divides the offset by 4, ulp-asm does not.

esp32ulp-elf-as:

ld r0, r0, 0
ld r1, r1, -4
ld r2, r2, -1

equivalent ulp-asm:

ld r0, r0, 0
ld r1, r1, -1
ld r2, r2, 0

jumpr instruction

Note that jumpr is an unsigned comparison.


esp32ulp-elf-as actually uses a different value for the step depending on whether you use a label for the step parameter or not.

Given the following code:

    test:
jumpr test, 1, lt
jumpr 10*4, 2, lt // esp32ulp-elf-as likes to divide by 4...

esp32ulp-elf-as will attempt to jump back by 1, then forward by 10. This is inconsistent because the label is a hard address, not a relative offset.

ulp-asm is consistent by instead taking in a hard address and converting it to a relative offset internally. So the equivalent code would be:

    test:
jumpr test, 1, lt
jumpr . + 10, 2, lt

The threshold parameter is not calculated correctly by esp32ulp-elf-as with large numbers. For example, the following does not compile:

jumpr step, 0xFFFF, lt

but the equivalent does:

jumpr step, -1, lt

ulp-asm fixes this.


The step parameter throws incorrect errors when the address is high in a given assembly file. For example, say the following is found at the end of a large assembly file:

  test:
jumpr test, 1, lt

esp32ulp-elf-as will say that the step is too large, even though it's clearly stepping back one instruction. ulp-asm fixes this.


esp32ulp-elf-as will compile the less than or equal condition jumpr target, threshold, le to:

jumpr target, threshold+1, lt

Which is fine unless threshold == 0xFFFF. ulp-asm does the same unless threshold == 0xFFFF, in which case it will use:

jumpr target, 0, ge // always jump

esp32ulp-elf-as will compile the greater than condition jumpr target, threshold, gt to:

jumpr target, threshold+1, ge

Which is fine unless threshold == 0xFFFF. ulp-asm does the same unless threshold == 0xFFFF, in which case it will use:

jumpr target, 0, lt // never jump

esp32ulp-elf-as will compile the equals condition jumpr target, threshold, eq to:

jumpr . + 8, threshold+1, ge // esp32ulp-elf-as likes to divide by 4...
jumpr target, threshold, ge

Which is fine unless threshold == 0xFFFF. ulp-asm does the same unless threshold == 0xFFFF, in which case it will use:

jumpr . + 2, 0xFFFF, lt
jumpr target, 0xFFFF, ge

Note that ulp-asm could instead use:

jumpr target, 0xFFFF, ge
jumpr target, 0xFFFF, ge

But the first fix has a higher probability on average of executing 1 instruction.

Also note that it uses 2 instructions because it needs to calculate the address of labels before doing math, which the threshold may use.

jumps instruction

Note that jumps is an unsigned comparison.

Also note that the esp32ulp-elf-as jumps implementation uses the same inconsistent address vs offset as its jumpr implementation. ulp-asm takes in a hard address, the same as its jumpr implementation.


esp32ulp-elf-as will compile the equals condition jumps target, threshold, gt to:

jumps . + 8, threshold, le // esp32ulp-elf-as likes to divide by 4...
jumps target, threshold, ge

This is correct but the same logic can be done in one instruction. ulp-asm will compile this to:

jumps target, threshold+1, ge

unless threshold == 0xFF, instead it will use:

jumps target, 0, lt // never jump

Grammar

The following is the grammar implemented:

ident   : [_.a-zA-Z0-9]*
label   : ident ":"
section : ".boot" | ".boot.data" | ".text" | ".data" | ".bss"
global  : ".global" ident
int : ".int" primary ( "," primary )*
directive : ( section | global | int )
newline : "\n"
splitter : newline | EOF

primary     : NUMBER | "." | ident | "(" expression ")"
unary       : "-" unary
            | primary
factor      : unary ( ( "/" | "*" ) unary )*
additive    : factor ( ( "-" | "+" ) factor )*
expression  : additive ( ( "<<" | ">>" ) additive )*
reg         : "r0" | "r1" | "r2" | "r3"
any         : ( reg | primary )

jump_cond  : "ov" | "eq"
jumpr_cond : "lt" | "le" | "gt" | "ge"
jumps_cond : "lt" | "le" | "gt" | "ge"

param0 : reg "," reg "," any
param1 : reg "," any
param2 : reg "," reg "," primary
param3 : any ( "," jump_cond )?
param4 : primary "," primary "," jumpr_cond
param5 : primary "," primary "," jumps_cond
param6 : primary
param7 : reg "," primary "," primary
param8 : primary "," primary "," primary "," primary
param9 : primary "," primary "," primary "," primary "," primary
param10: primary "," primary "," primary
param11: any

ins0     : "add" | "sub" | "and" | "or" | "lsh" | "rsh"
ins1     : "move"
ins2     : "st" | "ld"
ins3     : "jump"
ins4     : "jumpr"
ins5     : "jumps"
ins6     : "stage_inc" | "stage_dec" | "sleep" | "wait"
ins7     : "adc"
ins8     : "i2c_rd" | "reg_wr"
ins9     : "i2c_wr"
ins10    : "reg_rd"
ins11    : "call" // pseudo instruction, expands to "move r2, .+2; jump \any"
ins_none : "stage_rst" | "halt" | "wake"

ins     : ins0 param0
        | ins1 param1
        | ins2 param2
        | ins3 param3
        | ins4 param4
        | ins5 param5
        | ins6 param6
        | ins7 param7
        | ins8 param8
        | ins9 param9
        | ins10 param10
        | ins_none

statement : directive splitter
          | ins splitter
          | label

program: statement* EOF

Documentation

Overview

Copyright 2024 Blake Felt blake.w.felt@gmail.com

This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.

Copyright 2024 Blake Felt blake.w.felt@gmail.com

This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.

Copyright 2024 Blake Felt blake.w.felt@gmail.com

This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.

Copyright 2024 Blake Felt blake.w.felt@gmail.com

This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.

Copyright 2024 Blake Felt blake.w.felt@gmail.com

This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.

Copyright 2024 Blake Felt blake.w.felt@gmail.com

This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.

Copyright 2024 Blake Felt blake.w.felt@gmail.com

This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.

Copyright 2024 Blake Felt blake.w.felt@gmail.com

This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.

Index

Constants

View Source
const TEST_POSTLUDE = `
jump done
`
View Source
const TEST_PRELUDE = `` /* 2886-byte string literal not displayed */

Variables

This section is empty.

Functions

This section is empty.

Types

type Arg

type Arg interface {
	IsReg() bool
	IsJump() bool
	IsExpr() bool
	IsRelative() bool
}

type ArgExpr

type ArgExpr struct {
	Expr Expr
}

func (ArgExpr) IsExpr

func (a ArgExpr) IsExpr() bool

func (ArgExpr) IsJump

func (a ArgExpr) IsJump() bool

func (ArgExpr) IsReg

func (a ArgExpr) IsReg() bool

func (ArgExpr) IsRelative

func (a ArgExpr) IsRelative() bool

type ArgJump

type ArgJump struct {
	Arg Token
}

func (ArgJump) IsExpr

func (a ArgJump) IsExpr() bool

func (ArgJump) IsJump

func (a ArgJump) IsJump() bool

func (ArgJump) IsReg

func (a ArgJump) IsReg() bool

func (ArgJump) IsRelative

func (a ArgJump) IsRelative() bool

type ArgReg

type ArgReg struct {
	Reg Token
}

func (ArgReg) Evaluate

func (a ArgReg) Evaluate() (int, error)

func (ArgReg) IsExpr

func (a ArgReg) IsExpr() bool

func (ArgReg) IsJump

func (a ArgReg) IsJump() bool

func (ArgReg) IsReg

func (a ArgReg) IsReg() bool

func (ArgReg) IsRelative

func (a ArgReg) IsRelative() bool

func (ArgReg) ToExpr

func (a ArgReg) ToExpr() ArgExpr

type Assembler

type Assembler struct {
	Compiler Compiler
}

func (*Assembler) BuildAssembly

func (asm *Assembler) BuildAssembly(content string, name string, reservedBytes int, reduce bool) ([]byte, error)

func (*Assembler) BuildFile

func (asm *Assembler) BuildFile(content string, name string, reservedBytes int, reduce bool) ([]byte, error)

type Compiler

type Compiler struct {
	Labels map[string]*Label

	Boot           Section
	BootData       Section
	Text           Section
	Data           Section
	Bss            Section
	Stack          Section // data not placed here
	CurrentSection *Section
	// contains filtered or unexported fields
}

func (*Compiler) CompileToAsm

func (c *Compiler) CompileToAsm(program []Stmnt, reservedBytes int, reduce bool) ([]byte, error)

func (*Compiler) CompileToBin

func (c *Compiler) CompileToBin(program []Stmnt, reservedBytes int, reduce bool) ([]byte, error)

func (*Compiler) FormatSections

func (c *Compiler) FormatSections() string

type ExpectedTokenError

type ExpectedTokenError struct {
	// contains filtered or unexported fields
}

func (ExpectedTokenError) Error

func (e ExpectedTokenError) Error() string

type Expr

type Expr interface {
	Evaluate(map[string]*Label) (int, error)
	IsRelative() bool
}

type ExprBinary

type ExprBinary struct {
	Left     Expr
	Right    Expr
	Operator Token
}

func (ExprBinary) Evaluate

func (e ExprBinary) Evaluate(labels map[string]*Label) (int, error)

func (ExprBinary) IsRelative

func (exp ExprBinary) IsRelative() bool

func (ExprBinary) String

func (exp ExprBinary) String() string

type ExprLiteral

type ExprLiteral struct {
	Operator Token
}

func (ExprLiteral) Evaluate

func (e ExprLiteral) Evaluate(labels map[string]*Label) (int, error)

func (ExprLiteral) IsRelative

func (exp ExprLiteral) IsRelative() bool

func (ExprLiteral) String

func (exp ExprLiteral) String() string

type ExprUnary

type ExprUnary struct {
	Expression Expr
	Operator   Token
}

func (ExprUnary) Evaluate

func (e ExprUnary) Evaluate(labels map[string]*Label) (int, error)

func (ExprUnary) IsRelative

func (exp ExprUnary) IsRelative() bool

func (ExprUnary) String

func (exp ExprUnary) String() string

type FileRef

type FileRef struct {
	Filename string
	Line     int
	Index    int // the position within the line
}

func (FileRef) String

func (f FileRef) String() string

type GenericTokenError

type GenericTokenError struct {
	// contains filtered or unexported fields
}

func (GenericTokenError) Error

func (e GenericTokenError) Error() string

type InstrArgCountError

type InstrArgCountError struct {
	// contains filtered or unexported fields
}

func (InstrArgCountError) Error

func (e InstrArgCountError) Error() string

type InstrArgTypeError

type InstrArgTypeError struct {
	Stmnt StmntInstr
	ArgN  int // argument number
}

func (InstrArgTypeError) Error

func (e InstrArgTypeError) Error() string

type Label

type Label struct {
	Name   string
	Value  int
	Global bool
	// contains filtered or unexported fields
}

type ReduceIndex

type ReduceIndex struct {
	Index int         // the starting index
	Node  *ReduceTrie // the last node that this uses
}

func (*ReduceIndex) Next

func (r *ReduceIndex) Next(instruction StmntInstr) error

add the next instruction to the associated trie

type ReduceTrie

type ReduceTrie struct {
	Indexes   []int  // indexes that uses this statement
	Depth     int    // depth of this node
	Instr     string // the string version of this instruction
	TokenType token.Type
	Children  map[string]*ReduceTrie
	Parent    *ReduceTrie
	Final     bool // if this is a final node (a jump)
}

func (*ReduceTrie) AddChild

func (r *ReduceTrie) AddChild(index int, instruction StmntInstr) (*ReduceTrie, error)

func (*ReduceTrie) Max

func (r *ReduceTrie) Max() (*ReduceTrie, int, error)

func (*ReduceTrie) Value

func (r *ReduceTrie) Value() int

type Runner

type Runner struct {
	AssemblyName  string        // the "name" of the input assembly files
	ReservedBytes int           // the number of bytes reserved for the emulator
	Reduce        bool          // should the assembler perform code reduction
	Timeout       time.Duration // maximum time per test allowed
	Hardware      usb.Hardware  // the serial port (optional)
}

func (*Runner) Close

func (r *Runner) Close() error

func (*Runner) PortSet

func (r *Runner) PortSet() bool

Returns true if the serial port is set up.

func (*Runner) RunTest

func (r *Runner) RunTest(t *testing.T, asm string, expect string)

func (*Runner) RunTestWithHeader

func (r *Runner) RunTestWithHeader(t *testing.T, asm string, expect string)

func (*Runner) SetDefaults

func (r *Runner) SetDefaults()

Sets AssemblyName, ReservedBytes, and Reduce to a default.

func (*Runner) SetupPort

func (r *Runner) SetupPort() error

Set up the serial port based on the ESP_PORT environment variable. Returns an error if the port fails to open but not if ESP_PORT is not set. If you need to check whether the port is open, use `PortSet()`.

type Section

type Section struct {
	Size   int // size in bytes
	Bin    []byte
	Offset int // offset from start of program
}

func (*Section) Validate

func (s *Section) Validate(name string) error

type Stmnt

type Stmnt interface {
	Size() int // the size of this statement after compilation, in bytes
	Compile(map[string]*Label) ([]byte, error)
	CanReduce() bool     // can this statement be reduced?
	IsFinalReduce() bool // is this a final statement (a jump) in a reduction?
}

type StmntDirective

type StmntDirective struct {
	Directive Token
}

func (StmntDirective) CanReduce

func (s StmntDirective) CanReduce() bool

func (StmntDirective) Compile

func (s StmntDirective) Compile(labels map[string]*Label) ([]byte, error)

func (StmntDirective) IsFinalReduce

func (s StmntDirective) IsFinalReduce() bool

func (StmntDirective) Size

func (s StmntDirective) Size() int

type StmntGlobal

type StmntGlobal struct {
	Label Token
}

func (StmntGlobal) CanReduce

func (s StmntGlobal) CanReduce() bool

func (StmntGlobal) Compile

func (s StmntGlobal) Compile(labels map[string]*Label) ([]byte, error)

func (StmntGlobal) IsFinalReduce

func (s StmntGlobal) IsFinalReduce() bool

func (StmntGlobal) Size

func (s StmntGlobal) Size() int

func (StmntGlobal) String

func (s StmntGlobal) String() string

type StmntInstr

type StmntInstr struct {
	Instruction Token
	Args        []Arg
	// contains filtered or unexported fields
}

func (StmntInstr) CanReduce

func (s StmntInstr) CanReduce() bool

func (StmntInstr) Compile

func (s StmntInstr) Compile(labels map[string]*Label) ([]byte, error)

func (StmntInstr) IsFinalReduce

func (s StmntInstr) IsFinalReduce() bool

func (*StmntInstr) Setup

func (s *StmntInstr) Setup()

func (StmntInstr) Size

func (s StmntInstr) Size() int

func (*StmntInstr) String

func (s *StmntInstr) String() string

type StmntInt

type StmntInt struct {
	Args []ArgExpr
}

func (StmntInt) CanReduce

func (s StmntInt) CanReduce() bool

func (StmntInt) Compile

func (s StmntInt) Compile(labels map[string]*Label) ([]byte, error)

func (StmntInt) IsFinalReduce

func (s StmntInt) IsFinalReduce() bool

func (StmntInt) Size

func (s StmntInt) Size() int

func (StmntInt) String

func (s StmntInt) String() string

type StmntLabel

type StmntLabel struct {
	Label Token
}

func (StmntLabel) CanReduce

func (s StmntLabel) CanReduce() bool

func (StmntLabel) Compile

func (s StmntLabel) Compile(labels map[string]*Label) ([]byte, error)

func (StmntLabel) IsFinalReduce

func (s StmntLabel) IsFinalReduce() bool

func (StmntLabel) Size

func (s StmntLabel) Size() int

func (StmntLabel) String

func (s StmntLabel) String() string

type Token

type Token struct {
	TokenType token.Type // the type that this token represents
	Lexeme    string     // the original string
	Ref       FileRef    // the reference
	Number    int        // the number, if applicable
}

func (*Token) Equal

func (t *Token) Equal(other *Token) bool

func (Token) String

func (t Token) String() string

type UnfinishedError

type UnfinishedError struct {
	// contains filtered or unexported fields
}

func (UnfinishedError) Error

func (e UnfinishedError) Error() string

type UnknownIdentifierError

type UnknownIdentifierError struct {
	// contains filtered or unexported fields
}

func (UnknownIdentifierError) Error

func (e UnknownIdentifierError) Error() string

type UnknownTokenError

type UnknownTokenError struct {
	// contains filtered or unexported fields
}

func (UnknownTokenError) Error

func (e UnknownTokenError) Error() string

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL