cpet-561-01-coursework/notes.md
2024-09-18 09:51:11 -04:00

11 KiB

What is an embedded system?

It is more complex than a micro-controller, but it still has a singular particular task.

That being said, the line between embedded and micros is growing vague.

So what is the difference between embedded and general-purpose?

Embedded focus on power consumption, size, and reliability, and are not programmable by the end user. General Purpose computing is programmable by the end user, and so it has to be optimised for all cases ("faster is better").

Components of an embedded system

  • processor
  • interrupts
    • timer interrupts
    • illegal-operations
    • overvoltage
    • packet start from another device
  • memory
  • oscillator/clock gen
    • external from the processor
  • input/output

Embedded systems are built for dependability; 5 9s is laughable in embedded. Embedded systems also are often built to fit real-time constraints.

These are separated into hard and soft constraints.

ASSP: Application Specific Standard Parts

general purpose ASIC; think redrivers, USB drivers, audio systems. ASSPs are sold generally B-to-B

SOC: System on Chip

a monolithic chip that contains multiple pieces including the processor

SOPC: System on Programmable Chip SOC+FPGA

SOPC and SOC are relatively unoptimised, and have the same cost per volume, however redesigns are easy, so it is useful for development.


SOPC are designed by first picking the chip with the right hard-cores (pre-burned)

SERDE (serialize-deserialize) pins, cache, DSP logic, and "traditional" cores are these type

math logic and interfaces are generally soft-cores

The remaining resources are freely available to use in the SOPC for the designer. These are often scattered throughout the physical layout of the chip, so one job as a designer is to put stuff close together, or design ahead of time so it can work. Soft cores can sorta be moved around; they're more pre-designed circuits more than hard-cores, which are set in place.

So how do you know how much space you're using? Well, in most cases, you're either iterating on an old part, so you have a rough idea of what you're working with, or you are building a new part and already know your constraints.

PLAN BEFORE YOU CODE.

Don't assume you fully understand the problem, or the requirements. There are edge cases you WILL forget or not be told about.

Small designs are faster, common case should be fastest. Predictable characteristics are simpler in hardware.

Read the whole lab before just hopping through the thing beforehand.

Version control, version control, version control.

Labs are gonna build off each other, so make sure your cores work early.

ALWAYS DO THE DEMO, including the first one. Somebody always whiffs on the first one.

Brief History

Transistors started in ~1960, then ICs in 1965, then microprocessor in 1971.

Microprocessor vs microcontroller:

ALWAYS DO THE DEMO, including the first one. Somebody always whiffs on the first one.

Brief History

Transistors started in ~1960, then ICs in 1965, then microprocessor in 1971.

Microprocessor vs microcontroller.

Fetch -> Decode -> Execute -> Save -> Write-out -> Fetch -> ...

A microprocessor is a state machine that follows the above loop.

A microcontroller has a processor, RAM, ROM, etc etc all on one chip.

IP cores are already invented by somebody. They come in hard- and soft- flavours. Platform Designer has libraries of prebuilt cores, and does all the wiring for you already*.

Don't use more than 80% of the FPGA; you'll run into place-n-route issues.


Hardware design flow uses first and foremost HDLs like VHDL or Verilog. As such, it is done by Quartus. Software design flow uses primarily C and assembly. As such, it is performed by the NIOS-II EDS.

The FPGA configuration ultimately comes out as a .sof file, while the software configuration comes across as an ELF.

One of the most important objects to install is sysid, the System-ID peripheral, which is used to ensure consistent versioning from the software to the hardware.


NIOS-II Assembly

Human aliasing for machine instructions; specific to a given processor architecture (think the different RISC-V architecture subtypes).

Directives

Directives provide info to the assembler, its not actually an instruction, but is used to make your life easier. Directives always start with ., and include:

  • .end
  • .ascii (send ascii characters to memory)
  • .asciz (ascii with an appended zero bit)
  • .data
  • .equ symbol,expression (think #define in C/C++)
  • .global symbol
  • .iclude "filename"
  • .space size (allocates bytes for storage in memory; typically for reserving space for storing stuff later on)
  • .text
  • .word expression/.byte expression
    • Expressions separated by commas; expression is a 32-bit/8-bit numbers respectively
    • Example, can be used for storing display values for 7-seg displays

Labels

Assembler replaces each label with its memory adress when generating the executable. Must start at the beginning of the line with no leading spaces. ex. loop:

Registers

NIOSII has 32 32-bit internal registers. R0 always holds 0. Writing to R0 does nothig (functionally /dev/zero). Useful for clearing memory, bitmasking, and comparing to 0. R1 is for the assembler; Don't touch it. R24, R25, R27-R30 have specific internaluses and shouldn't be used unless you know what you're doing. R31 holds the return address for subroutine. At the most you'll tend to use 8-10 of them.

R27 is the stack pointer. R28 is the frame pointer.

Instruction Types

~100 instructions; in 3 different categories.

R-type

Calculations using 3 registers.

Add, sub, mul, div, and, or, nor, xor etc

ex. ADD R3, R4, R5

Adds R4 and R5, and stores the result in R3.

I-Type

Immediate; 2 registers and an immediate operand. This means you can use a constant rather than a second register.

Immediate operand is a 16-bit number. In NIOS-II, instructions are appended with i.

addi, subi, muli, andi, ori, xori, etc. No divi.

ex. addi R4, R4, 6 Add 6 to R4, assign back to R4.

J-type

No registers; think GOTO.

Common Instructions

Along with the above;

cmpeq (compare equal) cmpne (compare not equal) cmplt (compare lessthan)

Shift register commands exist.

br LABEL (Unconditional branch)

blt rA,rB,LABEL (branch if rA < rB) beq rA,rB,LABEL (branch if equal) unsigned comparison.

ldw r3,4(r5) (loads word from memory [defined as the address location stored in r5, +4]to r3) stw r3,0(r5) (stores word from r3, writes to the address stored in r5, +0) ldbio r5,0(r4) (load bit from IO, address r4+0, into r5) stbio r5,0(r8) (store bit from IO, address r8+0, into r5)

Data is stored in memory as byte-addressable. Little-endian; LSB in the smallest address.


Compiler Optimisations

  • Procedure in-lining: replacing functions with actual code to make life easier
  • Loop unrolling: Rather than having a true loop, if a loop is finite and known, you can turn it into a long string of executions, so its easier for the program counter.
  • Sub-expression elimination
  • Dead store eliminations
  • Code motion
  • dead code elimination
  • constant propagation
  • copy propagation
  • pipeline scheduling (reordering instructions)

These aren't always wanted. In RTOS, it might not meet timings. Switching from Debug build to release build may also break things in theory. Debug could be slowing down your code enough to work properly. Debugging code also needs to not have optimisations, so you know what's happening. The most common problem is missing volatile type qualifier.

gcc optimisations: -O0, -O1,... etc.

Good practices:

  • set registers and user input accesses as volatile
  • Allocate memory in advance
    • DON'T USE MALLOC
  • control pointers offsets
  • place constants on the left
    • If you miss an equals, it won't compile

C Review

Declaration vs Definition

Definitions define where a variable is created, and assigned storage at the same time. Declarations state the nature of the variable, but don't set the value. Variables must be declared, but not necessarily defined. int counter is a definition. Prototypes and extern calls are declarations.

Scope vs visibility

Data types

  • Basic (int, char, float, etc)
  • enumerated (arithmetic types with restricted values)
  • void (type with no value)
  • derived types (pointers, arrays, unions, etc)

Typedef = shortening of typing to make it easier to type

Preprocessor; part of the compiler, and reads a file line-by-line. Find-and-replace constant with value of constant. # line starters indicate preprocessor directives

Directives

#include

Used for imports (generally header files). Quotes for searching where source files are. <> for searching installed libraries. "#include is not a reference, it is a copy-paste." #ifndef MY_HEADER_H_ is useful to only include the header once.

#ifndef MY_HEADER_H_
#define "header.h"
#endif

#define

Used to define constant values so we don't have magic numbers. #define name value Leave as all upper case.

#if

Control the preprocessor with conditional statements

#if debug_enabled
//include this code
#else
//non-debug code
#endif

#ifdef

Checks if a symbol is defined, value doesn't matter

#if defined(symbol)

Similar to #ifdef, but allows for multiple checks

#if defined(FOO) || defined(BAR)
    //do something
#endif

#ifndef

If not defined.

Constants

Unsigned numbers are forced with a u suffix. Hex is prefixed with 0x. No binary representation. Leading zero with nothing else is interpreted as octal. Character constants; characters are values, use an ASCII chart. Escape characters; same as normal.

Type Qualifiers

Additional information about the data type. extern, volatile, etc. It can go before or after the datatype, but convention is before.

const

Constants are read-only memory, while #define replaces the value before compiling. Can't create a pointer to #define.

volatile

Values that can change outside the code's control. I/O, peripherals, interrupts.

restrict

Deprecated; restricts value to a certain space in memory.

Storage classes

auto is default, dw about it. register is a suggestion to store this value in a register, dw about it. static limits the visibility of the function, and will keep variable instantiation between function calls. Useful for only modifying values inside a function, but persisting the values.


extern specifies to specifically not make storage for it, and it must be defined exactly once as a global variable in another file. Its great, because you don't need to create a bunch more variables. But it's also terrible, because of potential race conditions, and non-obvious data connections.