diff --git a/build/Makefile b/build/Makefile index 9dcc5d8..190ce88 100644 --- a/build/Makefile +++ b/build/Makefile @@ -110,11 +110,14 @@ buildroot-clean: -docker container stop upsilon-buildroot -docker container rm upsilon-buildroot -###### TFTP +###### Execute tftp: cd ../boot && py3tftp --host 192.168.1.100 -p 6969 -v +copy: + scp ../boot/mmio.py ../linux/comm.py upsilon:~/ + ###### External projects clone: f4pga buildroot litex opensbi diff --git a/client/noise_test.py b/client/noise_test.py new file mode 100644 index 0000000..d7e54e5 --- /dev/null +++ b/client/noise_test.py @@ -0,0 +1,46 @@ +""" +Copyright 2023 (C) Peter McGoron + +This file is a part of Upsilon, a free and open source software project. +For license terms, refer to the files in `doc/copying` in the Upsilon +source distribution. +""" + +from pssh.clients import SSHClient +import numpy as np +import matplotlib.pyplot as plt +import pandas as pd +import sys + +def sign_extend(value, bits): + is_signed = (value >> (bits - 1)) & 1 == 1 + if not is_signed: + return value + return -((~value + 1) & ((1 << (bits - 1)) - 1)) + +client = SSHClient('192.168.1.50', user='root', pkey='~/.ssh/upsilon_key') +client.scp_send('../linux/noise_test.py', '/root/noise_test.py') +out = client.run_command('micropython noise_test.py') + +current_dac = None +current_adc = [] +x_ax = [] +y_ax = [] +for line in out.stdout: + l = line.split(' ') + if l[0] != current_dac: + if current_dac is not None: + m = np.mean(current_adc) + sdev = np.std(current_adc) + print(current_dac, m, sdev) + x_ax.append(current_dac) + y_ax.append(m) + current_adc = [sign_extend(int(l[1]), 18)] + current_dac = l[0] + else: + current_adc.append(sign_extend(int(l[1]),18)) + +df = pd.DataFrame({"x": x_ax, "y": y_ax}) +df.to_csv(f"{sys.argv[1]}.csv") +plt.plot(df.x, df.y) +plt.show() diff --git a/doc/controller_manual.md b/doc/controller_manual.md new file mode 100644 index 0000000..e522617 --- /dev/null +++ b/doc/controller_manual.md @@ -0,0 +1,46 @@ +Copyright 2023 (C) Peter McGoron. + +This file is a part of Upsilon, a free and open source software project. +For license terms, refer to the files in `doc/copying` in the Upsilon +source distribution. + +__________________________________________________________________________ + +This manual describes the controller software programming. This guide does not +describe client programming (programs that run on the client and interface with +the controller). It does not describe Verilog: see `verilog_manual.md` for +that. + +# Preqreuisites + +You must know basic Linux shell (change directories, edit files with `vi`) +and basic SSH usage (sftp, ssh). + +Knowledge of Micropython (a subset of Python) is required for scripting. + +I assume that you have the controller running and accessable. See `docker.md` +for the easy quick-start guide. + +# Programming in MicroPython + +## Introduction to MicroPython + +MicroPython is a programming language that is very similar to Python. It is +stripped down and designed to run on very small devices. If you have written +Python, you will be able to use MicroPython without issue. If you are not +a hardcore Python programmer, you might not even notice a difference. + +Everything you need to know is [here](https://docs.micropython.org). + +## Standard Library + +There are two modules of the standard library: `mmio` and `comm`. + +`mmio` are wrappers that handle reads and writes from MMIO pins. This file +is automatically generated by the build process. This file is generated in +the `gateware` directory (if you use the Docker build system, the file is +automatically copied to `boot/mmio.py`). + +`comm` contains higher level wrappers for DAC and ADC pins. This module is +documented well enough that you should be able to read it and understand +how to use it. diff --git a/doc/docker.md b/doc/docker.md index f595d25..bab82d9 100644 --- a/doc/docker.md +++ b/doc/docker.md @@ -116,3 +116,9 @@ UART. Run `litex_term /dev/ttyUSB1`. You should get messages in the window with the TFTP server that the FPGA has connected to the server. Eventually you will get a login prompt: you have sucessfully loaded Upsilon onto your FPGA. + +## Copy Library + +Run `make copy` to copy the Micropython Upsilon library to the FPGA. After +this the modules `comm` and `mmio` are available when running scripts in +`/root`. diff --git a/doc/user_manual.md b/doc/user_manual.md deleted file mode 100644 index 4b0bf0e..0000000 --- a/doc/user_manual.md +++ /dev/null @@ -1,27 +0,0 @@ -Copyright 2023 (C) Peter McGoron. - -This file is a part of Upsilon, a free and open source software project. -For license terms, refer to the files in `doc/copying` in the Upsilon -source distribution. - -__________________________________________________________________________ - -The User Manual is targeted towards non-programmers using Upsilon. - -# Preqreuisites - -You will need to know the basics of Git. Git is the system used to track -changes and update Upsilon. you will need to know the wwhat a git repository is, -how to pull changes from a repository, what commit hashes are and how to make -branches. - -You must know basic Linux shell (change directories, edit files with `vi`) -and basic SSH usage (sftp, ssh). - -Knowledge of Micropython (a subset of Python) is required for scripting. - - -# Building and Booting - -Follow `docs/docker.md` to setup the build environment, build Upsilon, and -boot Upsilon. diff --git a/gateware/csr2mp.py b/gateware/csr2mp.py index 3704e5f..3171b56 100644 --- a/gateware/csr2mp.py +++ b/gateware/csr2mp.py @@ -11,11 +11,43 @@ # # TODO: Devicetree? +import argparse import json import sys -class CSRGenerator: +class MMIORegister: + def __init__(self, name, read_only=False, number=1, exntype=None): + """ + Describes a MMIO register. + :param name: The name of the MMIO register. This name must be the + same as the pin name used in ``csr.json``, except for any + numerical suffix. + :param read_only: True if the register is read only. Defaults to + ``False``. + :param number: The number of MMIO registers with the same name + and number suffixes. The number suffixes must go from 0 + to ``number - 1`` with no gaps. + """ + self.name = name + self.read_only = read_only + self.number = number + self.exntype = exntype + +def mmio_factory(dac_num, exntype): + def f(name, read_only=False): + return MMIORegister(name, read_only, numer=dac_num, exntype=exntype) + return f + + +class MicroPythonCSRGenerator: def __init__(self, csrjson, bitwidthjson, registers, outf): + """ + This class generates a MicroPython wrapper for MMIO registers. + + :param csrjson: Filename of a LiteX "csr.json" file. + :param bitwidthjson: Filename of an Upsilon "bitwidthjson" file. + :param registers: A list of + """ self.registers = registers self.csrs = json.load(open(csrjson)) self.bws = json.load(open(bitwidthjson)) @@ -70,13 +102,17 @@ class CSRGenerator: assert len(acc) == 2 self.print(f'{indent}return {acc[0]} | ({acc[1]} << 32)\n') - def print_fun(self, optype, name, regnum, pfun): + def print_fun(self, optype, reg, pfun): """Print out a read/write function for an MMIO register. - * {optype} is set to "read" or "write" (the string). - * {name} is set to the name of the MMIO register, without number suffix. - * {regnum} is set to the amount of that type oF MMIO register exists. - * {pfun} is set to {self.print_write_register} or {self.print_read_register} + + :param optype: is set to "read" or "write" (the string). + :param reg: is the dictionary containing the register info. + :param pfun: is set to {self.print_write_register} or {self.print_read_register} """ + name = reg['name'] + regnum = reg['total'] + exntype = reg['exntype]' + self.print(f'def {optype}_{name}(') printed_argument = False @@ -101,26 +137,42 @@ class CSRGenerator: self.print(f'num == {i}:\n') pfun('\t\t', 'val', name, i) self.print(f'\telse:\n') - self.print(f'\t\traise Exception("invalid {name}", regnum)\n') + self.print(f'\t\traise {exntype}(regnum)\n') self.print('\n') def print_file(self): self.print('import machine\n') + self.print('class InvalidDACException(Exception):\n\tpass\n') + self.print('class InvalidADCException(Exception):\n\tpass\n') for reg in self.registers: - self.print_fun('read', reg['name'], reg['total'], self.print_read_register) + self.print_fun('read', reg, self.print_read_register) if not reg['read_only']: - self.print_fun('write', reg['name'], reg['total'], self.print_write_register) + self.print_fun('write', reg, self.print_write_register) if __name__ == "__main__": dac_num = 8 adc_num = 8 + dac_reg = mmio_factory(dac_num, "InvalidDACException") + adc_reg = mmio_factory(adc_num, "InvalidADCException") registers = [ - {"read_only": False, "name": "dac_sel", "total": dac_num}, - {"read_only": True, "name": "dac_finished", "total": dac_num}, - {"read_only": False, "name": "dac_arm", "total": dac_num}, - {"read_only": True, "name": "dac_recv_buf", "total": dac_num}, - {"read_only": False, "name": "dac_send_buf", "total": dac_num}, + dac_reg("dac_sel"), + dac_reg("dac_finished", read_only=True), + dac_reg("dac_arm"), + dac_reg("dac_recv_buf", read_only=True), + dac_reg("dac_send_buf"), + + adc_reg("adc_finished", read_only=True), + adc_reg("adc_arm"), + adc_reg("adc_recv_buf", read_only=True), + adc_reg("adc_sel"), + + MMIORegister("cl_in_loop", read_only=True), + MMIORegister("cl_cmd"), + MMIORegister("cl_word_in"), + MMIORegister("cl_start_cmd"), + MMIORegister("cl_finish_cmd", read_only=True), + # {"read_only": False, "name": "wf_arm", "total": dac_num}, # {"read_only": False, "name": "wf_halt_on_finish", "total": dac_num}, # {"read_only": True, "name": "wf_finished", "total": dac_num}, @@ -129,17 +181,5 @@ if __name__ == "__main__": # {"read_only": False, "name": "wf_refresh_start", "total": dac_num}, # {"read_only": True, "name": "wf_refresh_finished", "total": dac_num}, # {"read_only": False, "name": "wf_start_addr", "total": dac_num}, - - {"read_only": True, "name": "adc_finished", "total": adc_num}, - {"read_only": False, "name": "adc_arm", "total": adc_num}, - {"read_only": True, "name": "adc_recv_buf", "total": adc_num}, - {"read_only": False, "name": "adc_sel", "total": adc_num}, - - {"read_only": True, "name": "cl_in_loop", "total": 1}, - {"read_only": False, "name": "cl_cmd", "total": 1}, - {"read_only": False, "name": "cl_word_in", "total": 1}, - {"read_only": False, "name": "cl_word_out", "total": 1}, - {"read_only": False, "name": "cl_start_cmd", "total": 1}, - {"read_only": True, "name": "cl_finish_cmd", "total": 1}, ] - CSRGenerator("csr.json", "csr_bitwidth.json", registers, sys.stdout).print_file() + MicroPythonCSRGenerator("csr.json", "csr_bitwidth.json", registers, sys.stdout).print_file() diff --git a/gateware/soc.py b/gateware/soc.py index 9a49519..a54bcce 100644 --- a/gateware/soc.py +++ b/gateware/soc.py @@ -57,6 +57,9 @@ from litedram.frontend.dma import LiteDRAMDMAReader from liteeth.phy.mii import LiteEthPHYMII """ +Keep this diagram up to date! This is the wiring diagram from the ADC to +the named Verilog pins. + Refer to `A7-constraints.xdc` for pin names. DAC: SS MOSI MISO SCK 0: 1 2 3 4 (PMOD A top, right to left) diff --git a/linux/comm.py b/linux/comm.py index 8932344..0ee8479 100644 --- a/linux/comm.py +++ b/linux/comm.py @@ -1,25 +1,48 @@ +# Copyright 2023 (C) Peter McGoron +# This file is a part of Upsilon, a free and open source software project. +# For license terms, refer to the files in `doc/copying` in the Upsilon +# source distribution. +# +# Upsilon Micropython Standard Library. + from mmio import * -def dac_write_value(val, num): - write_dac_send_buf(1 << 20 | val & 0xFFFFF, num) # 20 bit DAC +# Write a 20 bit twos-complement value to a DAC. +def dac_write_volt(val, num): + """ + Write a 20 bit twos-complement value to a DAC. + + :param val: Two's complement 20 bit integer. The number is bitmasked + to the appropriate length, so negative Python integers are also + accepted. This DOES NOT check if the integer actually fits in 20 + bits. + + :param num: DAC number. + :raises Exception: + """ + write_dac_send_buf(1 << 20 | (val & 0xFFFFF), num) write_dac_arm(1, num) write_dac_arm(0, num) -def dac_read_value(val, num): +# Read a register from a DAC. +def dac_read_reg(val, num): write_dac_send_buf(1 << 23 | val, num) write_dac_arm(1, num) write_dac_arm(0, num) return read_dac_recv_buf(num) +# Initialize a DAC by setting it's output value to 0, and +# removing the output restriction from the settings register. def dac_init(num): write_dac_sel(0,num) - dac_write_value(0, num) - write_dac_send_buf(1 << 22 | 1 << 2, num) + dac_write_volt(0, num) + write_dac_send_buf(1 << 21 | 1 << 1, num) write_dac_arm(1, num) write_dac_arm(0, num) - return dac_read_value(1 << 22, num) + return dac_read_reg(1 << 21, num) -def adc_read_value(num): +# Read a value from an ADC. +def adc_read(num): write_adc_arm(1, num) write_adc_arm(0, num) - return read_from_adc(num) + return read_adc_recv_buf(num) diff --git a/linux/noise_test.py b/linux/noise_test.py new file mode 100644 index 0000000..b77994a --- /dev/null +++ b/linux/noise_test.py @@ -0,0 +1,8 @@ +from comm import * +from time import sleep_ms + +for i in range(-300,300): + dac_write_volt(i, 0) + for j in range(0,20): + print(i, adc_read(0)) +