diff --git a/regctl/Makefile b/regctl/Makefile new file mode 100644 index 0000000..cc9e3ae --- /dev/null +++ b/regctl/Makefile @@ -0,0 +1,172 @@ +# name of the executable +EXEC=regctl + +# list the c source files +SRCS=regctl.c + +# define the object files by using suffix replacement on the SRCS list +OBJS=$(SRCS:.c=.o) + +# directories where include files are located +INCLUDE_DIRS=. + +# put an "-I" in front of each include directory; +# this is the way GCC needs the include directories specified +INC_PARAMS=$(foreach d, $(INCLUDE_DIRS), -I$d) + +# build directories +BUILDDIR=build +X86BUILDDIR=$(BUILDDIR)/x86 +ARMBUILDDIR=$(BUILDDIR)/arm + +# executable directories +EXECDIR=exec +X86EXECDIR=$(EXECDIR)/x86 +ARMEXECDIR=$(EXECDIR)/arm + +# GCC flags +# static : use static instead of dynamic linking +# -g : retain debugging/symbol info in executable +# -Wall : enable all compilation warnings +# -std : which c standard to use +# -O : optimization level; 0 is no optimization +# -I : include directories where headers are located +CFLAGS=-static -g -Wall -std=gnu99 -O0 $(INC_PARAMS) + +# linker flags +# -g : retain debugging info +# -Wall : enable all compilation warnings +LDFLAGS=-g -Wall + +# arm cross compiler +CC_ARM=$(CROSS_COMPILE)gcc + +# x86 host compiler +CC_X86=gcc + +# Rule syntax: +# target: prerequisites +# recipe +# +# The target is the thing that gets created, prerequisites need to be executed +# before the target can be run, and the recpie defines how to create or update +# the target. Unless declared as phony, targets are assumed to be a real filename. +# Declaring a target as phony indicates that the target name is not a file that +# will be created. If a normal target already exists (e.g. the file or directory +# exists), then the target won't be run if the target is up to date. + +# phony target to create the build directories and both exectuables +.PHONY: all +all : dirs arm x86 + +# phony target to build for the ARM archiecture; first create build directories, +# then build the executable. +.PHONY: arm +ifdef CROSS_COMPILE +arm: armdirs $(ARMEXECDIR)/$(EXEC) +else +arm: + @echo "----------------------------------" + @echo "**not building arm target because CROSS_COMPILE isn't exported**" + @echo "----------------------------------" +endif + +# phony target to build for x86; first create build directories, then build +# the executable +.PHONY: x86 +x86: x86dirs $(X86EXECDIR)/$(EXEC) + +# target to build the ARM executable. The ARM object files are prereqs. +# The recipe runs gcc with the linker flags to make the binary. +# $^ is the list of all the prereqs, and $@ is the target +$(ARMEXECDIR)/$(EXEC): $(ARMBUILDDIR)/$(OBJS) + $(CC_ARM) $(LDFLAGS) $^ -o $@ + +# target to build the ARM objects from the c files (which are preqreqs); +# the recipe runs gcc with the cflags and creates the objects files for each +# source file. +$(ARMBUILDDIR)/$(OBJS): $(SRCS) + @echo "----------------------------------" + @echo "building for arm..." + @echo "----------------------------------" + $(CC_ARM) $(CFLAGS) -c $^ -o $@ + +# target to build the x86 exectuable; same as the equivalent ARM target +$(X86EXECDIR)/$(EXEC): $(X86BUILDDIR)/$(OBJS) + $(CC_X86) $(LDFLAGS) $^ -o $@ + +# target to build the x86 object files; same as the equivalent ARM target +$(X86BUILDDIR)/$(OBJS): $(SRCS) + @echo "----------------------------------" + @echo "building for x86 host..." + @echo "----------------------------------" + $(CC_X86) $(CFLAGS) -c $^ -o $@ + + +# phony target to make make build and executable directories if they don't +# already exist. +.PHONY: dirs +dirs: x86dirs armdirs + +# only create the arm directories if teh CROSS_COMPILE environment variable is +# defined +ifdef CROSS_COMPILE +.PHONY: armdirs +armdirs: $(ARMBUILDDIR) $(ARMEXECDIR) +else +armdirs: +endif + +# phony targer to create the x86 build directories +.PHONY: x86dirs +x86dirs: $(X86BUILDDIR) $(X86EXECDIR) + +# target to create the x86 build directory +$(X86BUILDDIR): + @echo "----------------------------------" + @echo "creating x86 directories..." + @echo "----------------------------------" + mkdir -p $@ + +# target to create the ARM build directory +$(ARMBUILDDIR): + @echo "----------------------------------" + @echo "creating arm directories..." + @echo "----------------------------------" + mkdir -p $@ + +# target to create the x86 executable directory +$(X86EXECDIR): + mkdir -p $@ + +# target to create the ARM executable directory +$(ARMEXECDIR): + mkdir -p $@ + + +# phony target to remove build files and executables +.PHONY: clean +clean: + @echo "----------------------------------" + @echo "removing build files..." + @echo "----------------------------------" + rm -rf $(BUILDDIR) + @echo "----------------------------------" + @echo "removing executable files..." + @echo "----------------------------------" + rm -rf $(EXECDIR) + +# phony target that just lists all available targets +.PHONY: help +help: + @echo "----------------------------------" + @echo "available targets:" + @echo "----------------------------------" + @echo "all: build for arm and x86" + @echo "arm: build for arm" + @echo "x86: build for x86" + @echo "dirs: create all build directories" + @echo "x86dirs: create x86 build directories" + @echo "armdirs: create arm build directories" + @echo "clean: remove build and exectuable files" + @echo "help: show this help text" diff --git a/regctl/README.md b/regctl/README.md new file mode 100644 index 0000000..7dcd902 --- /dev/null +++ b/regctl/README.md @@ -0,0 +1,53 @@ +# Register Control Program +`regctl` is a command line program that allows users to read/write registers by name, instead of by address like one would with `devmem`. This *userspace* program uses `mmap()` to access system memory, just like `devmem` does. This program is mostly useful as a slightly higher-level debugging/testing utility compared to using `devmem` (e.g. `busybox devmem` or `devmem2`). + +One nice thing about this program is that it handles conversion from human-readable numbers, like 0.34, to their fixed-point representations. + + + +## Usage +``` +Usage: regctl [OPTION...] write register_name register_value + or: regctl [OPTION...] read register_name +regctl -- read and write fpga fabric registers by name + + -l, --list-registers List available register names + -r, --readback Read back values after writing + -v, --verbose Produce verbose output + -?, --help Give this help list + --usage Give a short usage message +``` + +**Examples:** + +`./regctl -v write left_gain 0.2` + +`./regctl --readback --verbose write right_gain 0.732` + +`./regctl -v read left_gain` + +`./regctl -l` + +## Customization +Define your registers and component address/span in `registers.h`. Here's an example of register definitions: +```c +static reg_t registers[] = { + { + .name = "left_gain", + .width = 32, + .fraction_width = 28, + .is_signed = true, + .offset = LEFT_GAIN_OFFSET + }, + { + .name = "right_gain", + .width = 32, + .fraction_width = 28, + .is_signed = true, + .offset = RIGHT_GAIN_OFFSET + } +}; +``` + +## Compiling regctl +Run the Makefile. The only prerequisite is that you're on a Linux machine and have exported `CROSS_COMPILE=arm-linux-gnueabihf-` to cross compile the executable for ARM. The Makefile will by default create executables for both x86 and ARM. diff --git a/regctl/regctl.c b/regctl/regctl.c new file mode 100644 index 0000000..46a9722 --- /dev/null +++ b/regctl/regctl.c @@ -0,0 +1,442 @@ +/** @file regctl.c +Command line program to read/write register values by name. + +Define registers in registers.h + +@authors Trevor Vannoy, Aaron Koenigsberg + +Copyright 2020 Trevor Vannoy + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +*/ +#include +#include // mmap functions +#include // POSIX API +#include // error numbers +#include // exit function +#include // type definitions +#include // file control +#include // boolean types +#include +#include + +#include "regctl.h" +#include "registers.h" + +#define verbose_print(fmt, ...) \ + do { if (arguments.verbose) printf(fmt, __VA_ARGS__); } while (0) + +/****************************************************************************** +* Argument parsing +*******************************************************************************/ +// store the arguments/options given to the program +struct arguments +{ + char *command; + char *reg_name; + char *reg_val; + bool verbose; + bool readback; + bool list_registers; +}; + +// define program options/flags +static struct argp_option options[] = { + {"verbose", 'v', 0, 0, "Produce verbose output"}, + {"readback", 'r', 0, 0, "Read back values after writing"}, + {"list-registers", 'l', 0, 0, "List available register names"}, + {0} +}; + +// program documentation +static char doc[] = "regctl -- read and write fpga fabric registers by name"; +static char args_doc[] = "\ +write register_name register_value\n \ +read register_name"; + + +/****************************************************************************** +* Main program +*******************************************************************************/ +int main(int argc, char **argv) +{ + struct arguments arguments; + + // create the argument parser + struct argp argp = {options, parse_opt, args_doc, doc}; + + // parse arguments; parse_opt will process each argument + argp_parse(&argp, argc, argv, 0, 0, &arguments); + + // check to see if the supplied register name matches one of the registers + int reg_idx = -1; + for (int i=0; i < sizeof(registers)/sizeof(registers[0]); i++) + { + if (strcmp(registers[i].name, arguments.reg_name) == 0) + { + reg_idx = i; + } + } + + // if the register name didn't match, throw an error and exit + if (reg_idx == -1) + { + fprintf(stderr, "Register name \"%s\" didn't match any registers\n", arguments.reg_name); + list_registers(registers, sizeof(registers)/sizeof(reg_t)); + exit(EXIT_FAILURE); + } + + // open /dev/mem so we can access the register + int fd = open_devmem(); + + // map our fpga component's registers into our program's address space + uint32_t *base_addr = map_fpga_regs(fd, COMPONENT_BASE_ADDR, COMPONENT_SPAN); + + // compute the address for the register we are working with + uint32_t *reg = base_addr + registers[reg_idx].offset; + + // write to the register + if (strcmp("write", arguments.command) == 0) + { + // convert register value to a fixed-point integer + uint32_t val = set_fixed_num(arguments.reg_val, registers[reg_idx].fraction_width, registers[reg_idx].is_signed); + + // write the value to memory + verbose_print("Writing %s (0x%x) to %s at 0x%x\n", arguments.reg_val, + val, registers[reg_idx].name, registers[reg_idx].addr); + *reg = val; + + if (arguments.readback) + { + // convert fixed-point integer to string + char sval[100]; + fp_to_string(sval, val, registers[reg_idx].fraction_width, + registers[reg_idx].is_signed, NUM_PRINT_DECIMALS); + + printf("Readback: %s = %s\n", registers[reg_idx].name, sval); + verbose_print("Readback: stored integer value = 0x%x\n", val); + } + } + // read from the register + else if (strcmp("read", arguments.command) == 0) + { + verbose_print("Reading %s at 0x%x\n", + registers[reg_idx].name, registers[reg_idx].addr); + uint32_t val = *reg; + + // convert fixed-point integer to string + char sval[100]; // make the buffer plenty large... + fp_to_string(sval, val, registers[reg_idx].fraction_width, + registers[reg_idx].is_signed, NUM_PRINT_DECIMALS); + + verbose_print("Stored integer value = 0x%x\n", val); + printf("%s\n", sval); + } + + // unmap our register and close /dev/mem + cleanup(fd, base_addr); + + return 0; +} + +/****************************************************************************** +* Other functions... +*******************************************************************************/ +/* +Print available register names +*/ +static void list_registers(const reg_t *registers, const size_t num_registers) +{ + fprintf(stderr, "Available registers:\n"); + for (size_t i=0; i < num_registers; i++) + { + fprintf(stderr, "\t%s\n", registers[i].name); + } +} + +/* +Map the register space of our custom FPGA component +*/ +static uint32_t *map_fpga_regs( + const int fd, + const off_t offset, + const size_t span) +{ + // map our custom component into virtual memory + uint32_t *base_addr = (uint32_t *) mmap(NULL, span, + PROT_READ | PROT_WRITE,MAP_SHARED, fd, offset); + + // check for errors + if (base_addr == MAP_FAILED) + { + // capture the error number + int err = errno; + + fprintf(stderr, "ERROR: mmap() failed\n"); + fprintf(stderr, "ERRNO: %d\n", err); + + // cleanup and exit + close(fd); + exit(EXIT_FAILURE); + } + + // printf("base address = %p\n", base_addr); + return base_addr; +} + +/* +Open /dev/mem so we can access system memory +*/ +static int open_devmem() +{ + int fd = open("/dev/mem", O_RDWR | O_SYNC); + + // check for errors + if (fd < 0) + { + // capture the error number + int err = errno; + + fprintf(stderr, "ERROR: couldn't open /dev/mem\n"); + fprintf(stderr, "ERRNO: %d\n", err); + + exit(EXIT_FAILURE); + } + + return fd; +} + +/* +Unmap memory and close /dev/mem +*/ +static void cleanup(const int fd, uint32_t *addr) +{ + // unmap our register at addr + int result = munmap(addr, COMPONENT_SPAN); + + // check for errors + if (result < 0) + { + // capture the error number + int err = errno; + + fprintf(stderr, "ERROR: munmap() failed\n"); + fprintf(stderr, "ERRNO: %d\n", err); + + //cleanup and exit + close(fd); + exit(EXIT_FAILURE); + } + + // close devmem + close(fd); +} + +/* + arg option parser callback function +*/ +static error_t parse_opt(int key, char *arg, struct argp_state *state) +{ + // get our arguments struct + struct arguments *arguments = state->input; + // printf("key=%d, arg=%s, argnum=%d\n", key, arg, state->arg_num); + switch (key) + { + case 'v': + arguments->verbose = true; + break; + + case 'r': + arguments->readback = true; + break; + + case 'l': + arguments->list_registers = true; + list_registers(registers, sizeof(registers)/sizeof(reg_t)); + exit(EXIT_SUCCESS); + + case ARGP_KEY_INIT: + arguments->verbose = false; + arguments->readback = false; + arguments->list_registers = false; + arguments->reg_val = 0; + break; + + // positional arguments + case ARGP_KEY_ARG: + if (state->arg_num == 0) + { + if (strcmp("write", arg) !=0 && strcmp("read", arg) != 0) + { + argp_error(state, "command must be either read or write"); + } + arguments->command = arg; + } + else if (state->arg_num == 1) + { + arguments->reg_name = arg; + } + else if (state->arg_num == 2) + { + if (strcmp("write", arguments->command) == 0) + { + arguments->reg_val = arg; + } + } + break; + + case ARGP_KEY_END: + // printf("argp_key_end %d\n", state->arg_num); + if (state->arg_num <= 1) + { + argp_error(state, "not enough arguments"); + } + else if (strcmp("read", arguments->command) == 0 && state->arg_num > 2) + { + argp_error(state, "too many arguments for read command"); + } + else if (strcmp("write", arguments->command) == 0 && state->arg_num <= 2) + { + argp_error(state, "not enough arguments for write command"); + } + else if (state-> arg_num > 3) + { + argp_error(state, "too many arguments"); + } + break; + } + + return 0; +} + +/* + Turns a uint32_t interpreted as a fixed point into a string. +*/ +static int fp_to_string( + char * buf, + uint32_t fp_num, + const size_t fractional_bits, + const bool is_signed, + const uint8_t num_decimals) +{ + int buf_index = 0; + int int_mag = 1; + int int_part; + int frac_part; + int i = 0; + int32_t int_mask = 0x00000000; // intMask turns into a bitstring with 0's in the location of the integer part of fp_num + for (i = 0; i < fractional_bits; i++) { + int_mask = int_mask << 1; + int_mask += 1; + } + if (is_signed) { // if it signed, need to add '-' to the buffer as well as remove that bit from the bitstring + if (fp_num & 0x80000000) { + buf[buf_index++] = '-'; + } + fp_num = fp_num & 0x7fffffff; + } + int_part = (fp_num >> fractional_bits); // shift away the fractional bits + while (int_part / int_mag > 9) { // find the magnitude of the integer part + int_mag *= 10; + } + while (int_mag > 0) { // decrement the magnitude as we move one digit at a time from 'intPart' to the string + // common int to ascii conversion, since ints are sequential in ascii + buf[buf_index++] = (char)(int_part / int_mag + '0'); + int_part %= int_mag; // remove the first digit + int_mag /= 10; // decrease by one order of magnitude + } + buf[buf_index++] = '.'; + // get rid of the integer part. Also drop the last bit, not sure why this has to happen but if it doesn't there are some errors. + // I believe that this results in ever so slightly incorrectly translated nums, but idk + frac_part = (fp_num & int_mask) >> 1; + for (i = 0; i < num_decimals; ++i) { + frac_part *= 10; // shift the digit up, (maybe related to why dropping the last bit above?) + buf[buf_index++] = (frac_part >> (fractional_bits - 1)) + '0'; // inspect the digit that moved past the point + frac_part &= int_mask >> 1; // get rid of any bits that move past the point + } + buf[buf_index] = '\0'; + return buf_index; +} + +/* + Converts a given string to a uint32_t representation of a fixed point number. +*/ +static uint32_t set_fixed_num( + const char * s, + const int num_fractional_bits, + const bool is_signed) +{ + const int ARBITRARY_CUTOFF_LEN = 9; + int int_part_decimal = 0; + int frac_part_decimal = 0; + int frac_len = 0; + int frac_comp = 1; + int string_index = 0; + bool seen_point = false; + uint32_t accumulator = 0; + int i; + // get the info from the string + while (s[string_index] != '\0') { + if (s[string_index] == '.') { // if the point is found, need to switch from int accumulating to fraction + seen_point = true; + } + else if (string_index == 0 && s[0] == '-') { // if its the first char and its a negative sign, don't sweat + // I don't think anything needs to happen here. + } + else if (!seen_point) { + int_part_decimal *= 10; // shift digits left, then add the new digit + // common ascii to int conversion trick, since ints are sequential in ascii + int_part_decimal += (int)(s[string_index] - '0'); + } + else if (frac_len < ARBITRARY_CUTOFF_LEN) { // do not allow the len of the fraction to exceed 9. + frac_part_decimal *= 10; // shift digits left, then add the new digit + frac_part_decimal += (int)(s[string_index] - '0'); + // common ascii to int conversion trick, since ints are sequential in ascii + frac_len++; // need to keep track of the length + frac_comp *= 10; + } + else { + break; + } + string_index++; + } + + while (frac_len < ARBITRARY_CUTOFF_LEN) { // if the fraction len < 9 we want to make it 9 + frac_part_decimal *= 10; + frac_len++; + frac_comp *= 10; + } + // convert the decimal fraction to binary info. 32 is arbitrary, it is the precision of the conversion. extra + // precision beyond the number of fractional bits in the fixed point num will be truncated off. + for (i = 0; i < num_fractional_bits; i++) { + // if frac part divided by frac comp is greater than 1, a 1 should be appended to bitstring + if (frac_part_decimal / frac_comp) { + accumulator += 0x00000001; + frac_part_decimal -= frac_comp; + } + frac_part_decimal *= 2; + accumulator = accumulator << 1; + } + accumulator += int_part_decimal << num_fractional_bits; + if (is_signed && s[0] == '-') { + accumulator |= 0x80000000; // if its a signed int and theres a negative sign flip the first bit of accumulator + } + return accumulator; +} \ No newline at end of file diff --git a/regctl/regctl.h b/regctl/regctl.h new file mode 100644 index 0000000..4a96811 --- /dev/null +++ b/regctl/regctl.h @@ -0,0 +1,155 @@ +/** @file regctl.h + +@author Trevor Vannoy + +Copyright 2020 Trevor Vannoy + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +*/ +#ifndef REGCTL_H +#define REGCTL_H + +// number of decimal places to print when converting fixed-point to string +#define NUM_PRINT_DECIMALS 12 + +/****************************************************************************** +* Type definitions +*******************************************************************************/ +/** @struct reg_t +@brief structure containing register information (e.g. name, datatype, etc.) + +@var name, register name + +@var width, register width in bits + +@var fraction_width, number of fractional bits + +@var is_signed, whether the register is signed or unsigned + +@var offset, register address offset, in words, from the component base + +@var addr, physical register address, only used for diagnostics +*/ + +struct reg +{ + const char *name; + const uint8_t width; + const uint8_t fraction_width; + const bool is_signed; + const uint32_t offset; + const uint32_t addr; +} typedef reg_t; + +/****************************************************************************** +* Function prototypes +*******************************************************************************/ +/** parse_opt: argp option parser callback function. +This function gets called for every argument provided to the program. +See the argp documentation for more information. + +@param key, option key + +@param arg, the command line argument + +@param state, internal argp parser state + +@returns parser error code +*/ +static error_t parse_opt(int key, char *arg, struct argp_state *state); + +/** fp_to_string: Turns a uint32_t interpreted as a fixed point into a string. + +@param buf, buffer in which to fill the string. It is assumed to have enough space. If a buflen parameter were passed it would +be simple to add a check to make sure that no writing goes past the bound of the array. + +@param fp_num, the uint32_t to be interpreted as a fixed point to be translated into a string + +@param fractional_bits, the number of fractional bits for the interpretation. No consideration is made to check that it +is a sensible number. i.e. less than 32, more than 0. + +@param is_signed, whether or not to interpret the first bit as a sign. If true, the first bit is inspected then dumped. + +@param num_decimals, number of decimals to write to the string. + +@returns the length of the buffered string. +*/ +static int fp_to_string( + char * buf, + uint32_t fp_num, + const size_t fractional_bits, + const bool is_signed, + const uint8_t num_decimals +); + +/** set_fixed_num: Converts a given string to a uint32_t interpreted as a fixed point. + +@param s, the string to be converted. + +@param num_fractional_bits, the number of fractional bits in the fixed point. No consideration is made to validate. + +@param is_signed, whether or not to interpret the string as a signed or not. + +@returns returns a uint32_t that is a fixed point representation based on the num_fractional_bits and is_signed params. +*/ +static uint32_t set_fixed_num( + const char * s, + const int num_fractional_bits, + const bool is_signed +); + +/** open_devmem: Opens /dev/mem/ so we can access system memory. + +@returns a file descriptor pointing to /dev/mem +*/ +static int open_devmem(); + +/** cleanup: Unmap memory and close /dev/mem. + +@param fd, file descriptor pointing to /dev/mem + +@param addr, pointer to the memory to unmap +*/ +static void cleanup(const int fd, uint32_t *addr); + +/** map_fpga_regs: Map the register space of our custom FPGA component. + +@param fd, file descriptor to /dev/mem + +@param offset, base address of the custom FPGA component + +@param span, memory span of the custom FPGA component + +@returns pointer to the base address of the custom FPGA component +*/ +static uint32_t *map_fpga_regs( + const int fd, + const off_t base_addr, + const size_t span +); + +/** list_registers: Print available register names. + +@param registers, pointer to an array of register structs + +@param num_registers, number of registers in the array +*/ +static void list_registers(const reg_t *registers, const size_t num_registers); + +#endif // REGCTL_H \ No newline at end of file diff --git a/regctl/registers.h b/regctl/registers.h new file mode 100644 index 0000000..a989dd9 --- /dev/null +++ b/regctl/registers.h @@ -0,0 +1,90 @@ +/** @file regctl.h +FPGA component register defintions. + +This file contains addresses and information for registers in a custom FPGA +component. Change the definitions in this file to reflect your component. + +@author Trevor Vannoy + +Copyright 2020 Trevor Vannoy + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +*/ +#ifndef REGISTERS_H +#define REGISTERS_H + +#include "regctl.h" + +/****************************************************************************** +* Bus and component addresses +*******************************************************************************/ +// base address for the hps-to-fpga lightweight bridge +// this is the bus that our custom component is connected to +#define H2F_LW_BASE_ADDR 0xff200000 + +// offset from h2f_lw base as reported by Platform Designer +#define COMPONENT_OFFSET 0x0 + +// component base address as seen by the HPS +#define COMPONENT_BASE_ADDR (H2F_LW_BASE_ADDR + COMPONENT_OFFSET) + +// span of our custom component as reported by Platform Designer. +// this is computed as: last address - first address + 1 +#define COMPONENT_SPAN 0x8 + + +/****************************************************************************** +* Register definitions +*******************************************************************************/ +/* + Remember that each register is offset from each other by 4 bytes; + this is different from the vhdl view where each register is offset by 1 word + when we do the addressing on the avalon bus! + + Here we specify the offsets in words rather than bytes because we type cast + the base address returned by mmap to a uint32_t*. Thus when we increment that + pointer by 1, the memory address increments by 4 bytes since that's the size + of the type the pointer points to. + + See regctl.h for documentation on reg_t fields +*/ +#define LEFT_GAIN_OFFSET 0x0 +#define RIGHT_GAIN_OFFSET 0x1 + + +static reg_t registers[] = { + { + .name = "left_gain", + .width = 32, + .fraction_width = 28, + .is_signed = true, + .offset = LEFT_GAIN_OFFSET, + .addr = COMPONENT_BASE_ADDR + 4*LEFT_GAIN_OFFSET + }, + { + .name = "right_gain", + .width = 32, + .fraction_width = 28, + .is_signed = true, + .offset = RIGHT_GAIN_OFFSET, + .addr = COMPONENT_BASE_ADDR + 4*RIGHT_GAIN_OFFSET + } +}; + +#endif // REGISTERS_H \ No newline at end of file