zero scan and documentation

This commit is contained in:
Peter McGoron 2023-06-23 18:15:53 -04:00
parent 2b698fc08a
commit f30f6f1ad5
9 changed files with 211 additions and 63 deletions

View File

@ -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

46
client/noise_test.py Normal file
View File

@ -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()

46
doc/controller_manual.md Normal file
View File

@ -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.

View File

@ -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`.

View File

@ -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.

View File

@ -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()

View File

@ -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)

View File

@ -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)

8
linux/noise_test.py Normal file
View File

@ -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))