forked from M-Labs/urukul-pld
Compare commits
46 Commits
master
...
experiment
| Author | SHA1 | Date | |
|---|---|---|---|
| bdcaca2c8c | |||
| 4c9dcad22d | |||
| 0b6548a199 | |||
| a52e82ebf3 | |||
| 572388d2e8 | |||
| 6fde32a18c | |||
| 10b47f34af | |||
| fc355398c0 | |||
| ee0d605d81 | |||
| 6bcaa8f5bc | |||
| 54d287388d | |||
| 430eaa8e36 | |||
| 5c16c9408e | |||
| 9846f4e234 | |||
| 11369be803 | |||
| 8e7a9b726a | |||
| e732c3d555 | |||
| a6da8916cd | |||
| 683c389f86 | |||
| eedfe2cc5e | |||
| 3cd675607f | |||
| 3b396c49cb | |||
| 074700c02a | |||
| 2b9b8daf8c | |||
| c538e26b28 | |||
| 0d5a24d8ec | |||
| 7bda499a1e | |||
| 7039fdd594 | |||
| 9a23c50bba | |||
| 22ce5c8d52 | |||
| f47cdf1cf3 | |||
| f90efb785b | |||
| b327d6baac | |||
| 28a9e69c94 | |||
| 1b8e3c1c93 | |||
| 2218324930 | |||
| d257097cfd | |||
| bce3de2b64 | |||
| 4190cd490e | |||
| 1984e61f4f | |||
| 0eaff94df0 | |||
| e2158d5cf5 | |||
| 622a424888 | |||
| fd1e6a8bc0 | |||
| 99f51510c5 | |||
| e16b18441c |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
build/
|
||||
__pycache__/
|
||||
55
README.md
55
README.md
@@ -8,7 +8,21 @@
|
||||
|
||||
[NU-Servo](https://github.com/m-labs/nu-servo)
|
||||
|
||||
## Building
|
||||
## Supported Urukul Hardware Revision (HW_REV)
|
||||
|
||||
| PLD | HW_REV |
|
||||
|:--------:|:--------:|
|
||||
| XC2 CPLD | 1.4, 1.5 |
|
||||
| iCE40 | 1.6+ |
|
||||
|
||||
## Instructions for Xilinx CoolRunner-II CPLD (XC2 CPLD)
|
||||
|
||||
Sources are in the `xc2c` directory.
|
||||
```
|
||||
cd xc2c
|
||||
```
|
||||
|
||||
### Building
|
||||
|
||||
Needs [migen](https://github.com/m-labs/migen) and [Xilinx ISE](https://www.xilinx.com/products/design-tools/ise-design-suite.html). Assumes ISE is installed in ``/opt/Xilinx``.
|
||||
|
||||
@@ -16,7 +30,7 @@ Needs [migen](https://github.com/m-labs/migen) and [Xilinx ISE](https://www.xili
|
||||
make
|
||||
```
|
||||
|
||||
## Flashing
|
||||
### Flashing
|
||||
|
||||
With Digilent [JTAG HS2](https://store.digilentinc.com/jtag-hs2-programming-cable/) cable:
|
||||
|
||||
@@ -35,6 +49,43 @@ With Digilent [JTAG HS2](https://store.digilentinc.com/jtag-hs2-programming-cabl
|
||||
|
||||
- look for ``Verify: Success``
|
||||
|
||||
## Instructions for iCE40 FPGA
|
||||
|
||||
Sources are in the `ice40` directory.
|
||||
```
|
||||
cd ice40
|
||||
```
|
||||
|
||||
### Building
|
||||
|
||||
Needs [migen](https://github.com/m-labs/migen), ``yosys``, ``nextpnr``, ``icestorm``.
|
||||
|
||||
```
|
||||
python urukul.py # Builds non-SU-Servo firmware
|
||||
```
|
||||
To build the SU-Servo version of the firmware, add the `--suservo` argument when calling the script.
|
||||
|
||||
### Flashing
|
||||
|
||||
Use [kasli-i2c](https://git.m-labs.hk/M-Labs/kasli-i2c/src/branch/flash_urukul/).
|
||||
|
||||
Otherwise you can also use an FT232H-based board. Connect the wires as follows. Top left is closest to the CPLD, right is facing the edge. Bottom is towards the end of the edge.
|
||||
|
||||
```
|
||||
D4 D1
|
||||
D2 D0
|
||||
GND X
|
||||
X X
|
||||
D7 D6
|
||||
```
|
||||
|
||||
Use ``iceprog`` (available with ``icestorm`` package):
|
||||
|
||||
```
|
||||
iceprog build/urukul.bin # non-SU-Servo
|
||||
iceprog build/suservo.bin # SU-Servo
|
||||
```
|
||||
|
||||
# License
|
||||
|
||||
GPLv3+
|
||||
|
||||
44
flake.lock
generated
Normal file
44
flake.lock
generated
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1755593991,
|
||||
"narHash": "sha256-BA9MuPjBDx/WnpTJ0EGhStyfE7hug8g85Y3Ju9oTsM4=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "a58390ab6f1aa810eb8e0f0fc74230e7cc06de03",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-25.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs",
|
||||
"src-migen": "src-migen"
|
||||
}
|
||||
},
|
||||
"src-migen": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1749544952,
|
||||
"narHash": "sha256-NshlPiORBHWljSUP5bB7YBxe7k8dW0t8UXOsIq2EK8I=",
|
||||
"owner": "m-labs",
|
||||
"repo": "migen",
|
||||
"rev": "6e3a9e150fb006dabc4b55043d3af18dbfecd7e8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "m-labs",
|
||||
"repo": "migen",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
107
flake.nix
Normal file
107
flake.nix
Normal file
@@ -0,0 +1,107 @@
|
||||
{
|
||||
description = "CPLD/FPGA gateware on Urukul";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
|
||||
src-migen = {
|
||||
url = "github:m-labs/migen";
|
||||
flake = false;
|
||||
};
|
||||
};
|
||||
|
||||
outputs =
|
||||
{
|
||||
self,
|
||||
nixpkgs,
|
||||
src-migen,
|
||||
}:
|
||||
let
|
||||
pkgs = import nixpkgs {
|
||||
system = "x86_64-linux";
|
||||
};
|
||||
|
||||
migen = pkgs.python3Packages.buildPythonPackage rec {
|
||||
name = "migen";
|
||||
src = src-migen;
|
||||
pyproject = true;
|
||||
build-system = [ pkgs.python3Packages.setuptools ];
|
||||
propagatedBuildInputs = [ pkgs.python3Packages.colorama ];
|
||||
};
|
||||
|
||||
ise =
|
||||
let
|
||||
isePath = "/opt/Xilinx/14.7/ISE_DS";
|
||||
makeXilinxEnv =
|
||||
name:
|
||||
pkgs.buildFHSEnv {
|
||||
inherit name;
|
||||
targetPkgs =
|
||||
pkgs:
|
||||
(with pkgs; [
|
||||
ncurses5
|
||||
zlib
|
||||
libuuid
|
||||
xorg.libSM
|
||||
xorg.libICE
|
||||
xorg.libXrender
|
||||
xorg.libX11
|
||||
xorg.libXext
|
||||
xorg.libXtst
|
||||
xorg.libXi
|
||||
]);
|
||||
profile = ''
|
||||
source ${isePath}/common/.settings64.sh ${isePath}/common
|
||||
source ${isePath}/ISE/.settings64.sh ${isePath}/ISE
|
||||
'';
|
||||
runScript = name;
|
||||
};
|
||||
in
|
||||
pkgs.lib.attrsets.genAttrs [ "xst" "ngdbuild" "cpldfit" "taengine" "hprep6" ] makeXilinxEnv;
|
||||
|
||||
in
|
||||
rec {
|
||||
packages.x86_64-linux = {
|
||||
inherit migen;
|
||||
|
||||
urukul-xc2c = pkgs.stdenv.mkDerivation {
|
||||
name = "urukul-xc2c";
|
||||
src = self;
|
||||
buildInputs = [ (pkgs.python3.withPackages (ps: [ migen ])) ] ++ (builtins.attrValues ise);
|
||||
buildPhase = "python xc2c/urukul_impl.py";
|
||||
installPhase = ''
|
||||
mkdir -p $out $out/nix-support
|
||||
cp build/urukul.jed $out
|
||||
echo file binary-dist $out/urukul.jed >> $out/nix-support/hydra-build-products
|
||||
'';
|
||||
};
|
||||
|
||||
urukul-ice40 = pkgs.stdenv.mkDerivation {
|
||||
name = "urukul-ice40";
|
||||
src = self;
|
||||
buildInputs = [
|
||||
(pkgs.python3.withPackages (ps: [ migen ]))
|
||||
]
|
||||
++ (with pkgs; [
|
||||
yosys
|
||||
nextpnr
|
||||
icestorm
|
||||
]);
|
||||
buildPhase = ''
|
||||
python ice40/urukul.py
|
||||
python ice40/urukul.py --suservo
|
||||
'';
|
||||
installPhase = ''
|
||||
mkdir -p $out $out/nix-support
|
||||
cp build/urukul.bin $out
|
||||
cp build/suservo.bin $out
|
||||
echo file binary-dist $out/urukul.bin >> $out/nix-support/hydra-build-products
|
||||
echo file binary-dist $out/suservo.bin >> $out/nix-support/hydra-build-products
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
formatter.x86_64-linux = pkgs.nixfmt-tree;
|
||||
|
||||
hydraJobs = packages.x86_64-linux;
|
||||
};
|
||||
}
|
||||
158
ice40/platform.py
Normal file
158
ice40/platform.py
Normal file
@@ -0,0 +1,158 @@
|
||||
from migen.build.generic_platform import *
|
||||
from migen.build.lattice import LatticePlatform
|
||||
|
||||
_io = [
|
||||
("clk25", 0, Pins("K9")),
|
||||
("tp", 0, Pins("H14")),
|
||||
("tp", 1, Pins("J16")),
|
||||
("tp", 2, Pins("J15")),
|
||||
("tp", 3, Pins("K16")),
|
||||
("tp", 4, Pins("K15")),
|
||||
("ifc_mode", 0, Pins("E16 D16 D15 C16")),
|
||||
# two more user switches marked SW2 on PCB
|
||||
("sw", 0, Pins("F15 F16")),
|
||||
("hw_rev", 0, Pins("R3 T2 R2 T1")),
|
||||
# 10k low: AD9912, 0R high: AD9910
|
||||
("variant", 0, Pins("T3")),
|
||||
("err", 0, Pins("L7")),
|
||||
(
|
||||
"clk",
|
||||
0,
|
||||
Subsignal("div", Pins("P6")),
|
||||
Subsignal("in_sel", Pins("P5")),
|
||||
Subsignal("mmcx_osc_sel", Pins("M8")),
|
||||
Subsignal("osc_en_n", Pins("T5")),
|
||||
),
|
||||
(
|
||||
"dds_common",
|
||||
0,
|
||||
Subsignal("master_reset", Pins("C7")),
|
||||
Subsignal("io_reset", Pins("E14")),
|
||||
),
|
||||
(
|
||||
"dds_sync",
|
||||
0,
|
||||
Subsignal("clk0", Pins("B4")), # DDS_SYNC_CLK0
|
||||
Subsignal("clk_out_en", Pins("N6")), # DDS_SYNC_CLK_OUTEN
|
||||
Subsignal("sync_sel", Pins("N5")), # DDS_SYNC_CLKSEL
|
||||
Subsignal("sync_out_en", Pins("N7")), # DDS_SYNC_OUTEN
|
||||
),
|
||||
(
|
||||
"att",
|
||||
0,
|
||||
Subsignal("clk", Pins("B3")),
|
||||
Subsignal("rst_n", Pins("D8")),
|
||||
Subsignal("le", Pins("D13 C11 D4 A2")),
|
||||
Subsignal("s_in", Pins("C13 C12 C5 A1")),
|
||||
Subsignal("s_out", Pins("C14 E11 C4 F9")),
|
||||
),
|
||||
(
|
||||
"dds",
|
||||
0,
|
||||
Subsignal("rf_sw", Pins("A7")),
|
||||
Subsignal("led", Pins("G13 G14")),
|
||||
Subsignal("smp_err", Pins("F7")),
|
||||
Subsignal("pll_lock", Pins("E5")),
|
||||
Subsignal("io_update", Pins("B12")),
|
||||
Subsignal("profile", Pins("A15 B13 B14")),
|
||||
Subsignal("osk", Pins("A16")),
|
||||
Subsignal("drover", Pins("B15")),
|
||||
Subsignal("drhold", Pins("D14")),
|
||||
Subsignal("drctl", Pins("B16")),
|
||||
Subsignal("reset", Pins("B11")),
|
||||
Subsignal("sck", Pins("B8")),
|
||||
Subsignal("sdo", Pins("A11")),
|
||||
Subsignal("sdi", Pins("A10")),
|
||||
Subsignal("cs_n", Pins("B10")),
|
||||
),
|
||||
(
|
||||
"dds",
|
||||
1,
|
||||
Subsignal("rf_sw", Pins("A6")),
|
||||
Subsignal("led", Pins("F14 F13")),
|
||||
Subsignal("smp_err", Pins("D11")),
|
||||
Subsignal("pll_lock", Pins("C10")),
|
||||
Subsignal("io_update", Pins("L16")),
|
||||
Subsignal("profile", Pins("J14 K14 J10")),
|
||||
Subsignal("osk", Pins("H12")),
|
||||
Subsignal("drover", Pins("G15")),
|
||||
Subsignal("drhold", Pins("B9")),
|
||||
Subsignal("drctl", Pins("G16")),
|
||||
Subsignal("reset", Pins("A9")),
|
||||
Subsignal("sck", Pins("G11")),
|
||||
Subsignal("sdo", Pins("N16")),
|
||||
Subsignal("sdi", Pins("M16")),
|
||||
Subsignal("cs_n", Pins("M15")),
|
||||
),
|
||||
(
|
||||
"dds",
|
||||
2,
|
||||
Subsignal("rf_sw", Pins("C8")),
|
||||
Subsignal("led", Pins("E13 G12")),
|
||||
Subsignal("smp_err", Pins("B5")),
|
||||
Subsignal("pll_lock", Pins("P4")),
|
||||
Subsignal("io_update", Pins("C6")),
|
||||
Subsignal("profile", Pins("E9 E6 D7")),
|
||||
Subsignal("osk", Pins("E10")),
|
||||
Subsignal("drover", Pins("D9")),
|
||||
Subsignal("drhold", Pins("D10")),
|
||||
Subsignal("drctl", Pins("C9")),
|
||||
Subsignal("reset", Pins("B7")),
|
||||
Subsignal("sck", Pins("C3")),
|
||||
Subsignal("sdo", Pins("D5")),
|
||||
Subsignal("sdi", Pins("A5")),
|
||||
Subsignal("cs_n", Pins("D6")),
|
||||
),
|
||||
(
|
||||
"dds",
|
||||
3,
|
||||
Subsignal("rf_sw", Pins("B6")),
|
||||
Subsignal("led", Pins("H13 F11")),
|
||||
Subsignal("smp_err", Pins("P15")),
|
||||
Subsignal("pll_lock", Pins("P16")),
|
||||
Subsignal("io_update", Pins("M14")),
|
||||
Subsignal("profile", Pins("L12 M13 R14")),
|
||||
Subsignal("osk", Pins("J12")),
|
||||
Subsignal("drover", Pins("J11")),
|
||||
Subsignal("drhold", Pins("G10")),
|
||||
Subsignal("drctl", Pins("H11")),
|
||||
Subsignal("reset", Pins("J13")),
|
||||
Subsignal("sck", Pins("K13")),
|
||||
Subsignal("sdo", Pins("L13")),
|
||||
Subsignal("sdi", Pins("L14")),
|
||||
Subsignal("cs_n", Pins("R15")),
|
||||
),
|
||||
# All EEM I/O are in LVDS standard. P pin corresponds to port A of LVDS pair.
|
||||
# N pin/port B must not be requested as per iCE40 docs.
|
||||
# See Figure 9. Differential I/O Design Example.
|
||||
# EEM 0:P
|
||||
("eem_p", 0, Pins("G1")), # SCLK
|
||||
("eem_p", 1, Pins("M1")), # MOSI
|
||||
("eem_p", 2, Pins("K3")), # MISO/NU_CLK
|
||||
("eem_p", 3, Pins("H2")), # CS0
|
||||
("eem_p", 4, Pins("G2")), # CS1
|
||||
("eem_p", 5, Pins("E3")), # CS2/NU_CS
|
||||
("eem_p", 6, Pins("D2")), # IO_UPDATE
|
||||
("eem_p", 7, Pins("B2")), # DDS_RESET/SYNC_DAT
|
||||
# EEM 1:P
|
||||
("eem_p", 8, Pins("H1")), # SYNC_CLK/NU_MOSI0
|
||||
("eem_p", 9, Pins("L1")), # SYNC_IN/NU_MOSI1
|
||||
("eem_p", 10, Pins("J1")), # IO_UPDATE_RET/NU_MOSI2
|
||||
("eem_p", 11, Pins("F2")), # NU_MOSI3
|
||||
("eem_p", 12, Pins("E2")), # SW0
|
||||
("eem_p", 13, Pins("D1")), # SW1
|
||||
("eem_p", 14, Pins("C2")), # SW2
|
||||
("eem_p", 15, Pins("B1")), # SW3
|
||||
|
||||
# Remaining N pins are for LVDS outputs
|
||||
("eem_n", 2, Pins("K1")), # MISO
|
||||
("eem_n", 10, Pins("J2")), # IO_UPDATE_RET
|
||||
]
|
||||
|
||||
|
||||
class Platform(LatticePlatform):
|
||||
default_clk_name = "clk25"
|
||||
default_clk_period = 40.0
|
||||
|
||||
def __init__(self):
|
||||
LatticePlatform.__init__(self, "ice40-hx8k-ct256", _io, toolchain="icestorm")
|
||||
587
ice40/urukul.py
Normal file
587
ice40/urukul.py
Normal file
@@ -0,0 +1,587 @@
|
||||
from migen import *
|
||||
from migen.genlib.io import DifferentialInput, DifferentialOutput
|
||||
from migen.genlib.coding import Encoder
|
||||
|
||||
|
||||
# increment this if the behavior (LEDs, registers, EEM pins) changes
|
||||
__proto_rev__ = 9
|
||||
|
||||
|
||||
class SR(Module):
|
||||
"""
|
||||
Shift register, SPI slave
|
||||
|
||||
* CPOL = 0 (clock idle low during ~SEL)
|
||||
* CPHA = 0 (sample on first edge, shift on second)
|
||||
* SPI mode 0
|
||||
* samples SDI on rising clock edges (SCK1 domain)
|
||||
* shifts out SDO on falling clock edges (SCK0 domain)
|
||||
* MSB first
|
||||
* the first output bit (MSB) is undefined
|
||||
* the first output bit is available from the start of the SEL cycle until
|
||||
the first falling edge
|
||||
* the first input bit is sampled on the first rising edge
|
||||
* on the first rising edge with SEL assered, the parallel data DO
|
||||
is loaded into the shift register
|
||||
* following at least one rising clock edge, on the deassertion of SEL,
|
||||
the shift register is loaded into the parallel data register DI
|
||||
"""
|
||||
|
||||
def __init__(self, width):
|
||||
self.sdi = Signal()
|
||||
self.sdo = Signal()
|
||||
self.sel = Signal()
|
||||
|
||||
self.di = Signal(width)
|
||||
self.do = Signal(width)
|
||||
|
||||
# # #
|
||||
|
||||
sr = Signal(width)
|
||||
|
||||
self.clock_domains.cd_le = ClockDomain("le", reset_less=True)
|
||||
# clock the latch domain from selection deassertion but only after
|
||||
# there was a serial clock edge with asserted select (i.e. ignore
|
||||
# glitches).
|
||||
self.specials += Instance("SB_DFFES", i_D=0, i_C=ClockSignal("sck1"),
|
||||
i_E=self.sel, i_S=~self.sel, o_Q=self.cd_le.clk)
|
||||
self.sync.sck0 += [
|
||||
If(self.sel,
|
||||
self.sdo.eq(sr[-1]),
|
||||
),
|
||||
]
|
||||
self.sync.sck1 += [
|
||||
If(self.sel,
|
||||
sr[0].eq(self.sdi),
|
||||
If(self.cd_le.clk,
|
||||
sr[1:].eq(self.do[:-1])
|
||||
).Else(
|
||||
sr[1:].eq(sr[:-1])
|
||||
)
|
||||
)
|
||||
]
|
||||
self.sync.le += [
|
||||
self.di.eq(sr)
|
||||
]
|
||||
|
||||
|
||||
class CFG(Module):
|
||||
"""Configuration register
|
||||
|
||||
The configuration register is updated from the SPI shift register on the
|
||||
deselection of the CPLD at the end of the SPI transaction.
|
||||
The initial state is 0 (all bits cleared).
|
||||
The bits in the configuration register (from LSB to MSB) are:
|
||||
|
||||
| Name | Width | Function |
|
||||
|-----------+-------+-------------------------------------------------|
|
||||
| RF_SW | 4 | Activates RF switch (per channel) |
|
||||
| LED | 4 | Activates the red LED (per channel) |
|
||||
| PROFILE | 4 * 3 | Controls DDS.PROFILE[0:2] (per channel) |
|
||||
| OSK | 4 | Asserts DDS.OSK (per channel) |
|
||||
| DRCTL | 4 | Asserts DDS.DRCTL (per channel) |
|
||||
| DRHOLD | 4 | Asserts DDS.DRHOLD (per channel) |
|
||||
| IO_UPDATE | 4 | Asserts DDS.IO_UPDATE (per channel), where |
|
||||
| | | CFG.MASK_NU is high |
|
||||
| MASK_NU | 4 | Disables DDS from QSPI interface, disables |
|
||||
| | | IO_UPDATE control through IO_UPDATE EEM signal, |
|
||||
| | | enables access through CS=3, enables control of |
|
||||
| | | IO_UPDATE through CFG.IO_UPDATE[0:3] |
|
||||
| CLK_SEL0 | 1 | Selects CLK source: 0 MMCX/OSC, 1 SMA |
|
||||
| SYNC_SEL | 1 | Selects SYNC source |
|
||||
| RST | 1 | Asserts DDS[0:3].RESET, DDS[0:3].MASTER_RESET, |
|
||||
| | | ATT[0:3].RST |
|
||||
| IO_RST | 1 | Asserts DDS[0:3].IO_RESET |
|
||||
| CLK_SEL1 | 1 | Selects CLK source: 0 OSC, 1 MMCX |
|
||||
| DIV | 2 | Clock divider configuration: 0: default, |
|
||||
| | | 1: divide-by-one, 2: divider-by-two, |
|
||||
| | | 3: divide-by-four |
|
||||
| ATT_EN | 4 | Enable ATT (per channel) |
|
||||
"""
|
||||
|
||||
def __init__(self, platform, n=4):
|
||||
self.data = Record(
|
||||
[
|
||||
("rf_sw", n),
|
||||
("led", n),
|
||||
("profile", 3 * n),
|
||||
("osk", n),
|
||||
("drctl", n),
|
||||
("drhold", n),
|
||||
("io_update", n),
|
||||
("mask_nu", 4),
|
||||
("clk_sel0", 1),
|
||||
("sync_sel", 1),
|
||||
("rst", 1),
|
||||
("io_rst", 1),
|
||||
("clk_sel1", 1),
|
||||
("div", 2),
|
||||
("att_en", n),
|
||||
]
|
||||
)
|
||||
dds_common = platform.lookup_request("dds_common")
|
||||
dds_sync = platform.lookup_request("dds_sync")
|
||||
att = platform.lookup_request("att")
|
||||
clk = platform.lookup_request("clk")
|
||||
self.en_9910 = Signal()
|
||||
|
||||
self.comb += [
|
||||
clk.in_sel.eq(self.data.clk_sel0),
|
||||
clk.mmcx_osc_sel.eq(self.data.clk_sel1),
|
||||
clk.osc_en_n.eq(clk.in_sel | clk.mmcx_osc_sel),
|
||||
dds_sync.sync_sel.eq(self.data.sync_sel),
|
||||
dds_common.master_reset.eq(self.data.rst),
|
||||
dds_common.io_reset.eq(self.data.io_rst),
|
||||
att.rst_n.eq(~self.data.rst),
|
||||
]
|
||||
|
||||
for i in range(n):
|
||||
sw = Signal()
|
||||
self.specials += DifferentialInput(platform.request("eem_p", 12 + i), Signal(), sw)
|
||||
dds = platform.lookup_request("dds", i)
|
||||
self.comb += [
|
||||
dds.rf_sw.eq(sw | self.data.rf_sw[i]),
|
||||
dds.led[0].eq(dds.rf_sw), # green
|
||||
dds.led[1].eq(self.data.led[i] | (self.en_9910 & (
|
||||
dds.smp_err | ~dds.pll_lock))), # red
|
||||
dds.profile.eq(self.data.profile[3 * i : 3 * i + 3]),
|
||||
dds.osk.eq(self.data.osk[i]),
|
||||
dds.drhold.eq(self.data.drhold[i]),
|
||||
dds.drctl.eq(self.data.drctl[i]),
|
||||
]
|
||||
|
||||
|
||||
class Status(Module):
|
||||
"""Status register.
|
||||
|
||||
The status data is loaded into the SPI shift register on the first
|
||||
rising SCK edge while the CPLD is selected. The bits from LSB to MSB
|
||||
are:
|
||||
|
||||
| Name | Width | Function |
|
||||
|-----------+-------+-------------------------------------------|
|
||||
| RF_SW | 4 | Actual RF switch and green LED activation |
|
||||
| | | (including that by EEM1.SW[0:3]) |
|
||||
| SMP_ERR | 4 | DDS.SMP_ERR per channel |
|
||||
| PLL_LOCK | 4 | DDS.PLL_LOCK per channel |
|
||||
| IFC_MODE | 4 | IFC_MODE[0:3] |
|
||||
| PROTO_REV | 7 | Protocol revision (see __proto_rev__) |
|
||||
| DROVER | 4 | DDS.DROVER per channel |
|
||||
"""
|
||||
|
||||
def __init__(self, platform, n=4):
|
||||
self.data = Record(
|
||||
[
|
||||
("rf_sw", n),
|
||||
("smp_err", n),
|
||||
("pll_lock", n),
|
||||
("ifc_mode", 4),
|
||||
("proto_rev", 7),
|
||||
("drover", n),
|
||||
]
|
||||
)
|
||||
self.comb += [
|
||||
self.data.ifc_mode.eq(platform.lookup_request("ifc_mode")),
|
||||
self.data.proto_rev.eq(__proto_rev__),
|
||||
]
|
||||
for i in range(n):
|
||||
dds = platform.lookup_request("dds", i)
|
||||
self.comb += [
|
||||
self.data.rf_sw[i].eq(dds.rf_sw),
|
||||
self.data.smp_err[i].eq(dds.smp_err),
|
||||
self.data.pll_lock[i].eq(dds.pll_lock),
|
||||
self.data.drover[i].eq(dds.drover),
|
||||
]
|
||||
|
||||
|
||||
class Urukul(Module):
|
||||
"""
|
||||
Urukul IO router and configuration/status
|
||||
=========================================
|
||||
|
||||
The CPLD controls/monitors:
|
||||
|
||||
* the four AD9912 or AD9910 DDS (SPI, status, reset, IO update)
|
||||
* the four digitally controlled RF step attenuators (SPI, reset)
|
||||
* the four RF switches
|
||||
* the clock input tree (division and clock selection)
|
||||
* the synchronization tree (sync source selection, sync clock output,
|
||||
sync drive)
|
||||
* the eight LEDs
|
||||
* the two EEM connectors
|
||||
* the test pads
|
||||
* the four configuration switches
|
||||
|
||||
Pin Out
|
||||
-------
|
||||
|
||||
Urukul operates from one or two EEM connectors. In standard SPI mode, the
|
||||
complete Urukul functionality can be accessed using only that interface.
|
||||
Standard SPI mode only needs the second EEM connector to interface with
|
||||
high resolution RF switching and synchronization signals. SU-Servo mode
|
||||
always requires two EEM connectors.
|
||||
|
||||
| EEM | LVDS pair | PCB net | Standalone | SU-Servo |
|
||||
|------+-----------+---------+---------------------+-----------|
|
||||
| EEM0 | 0 | A0 | SCLK | SCLK |
|
||||
| EEM0 | 1 | A1 | MOSI | MOSI |
|
||||
| EEM0 | 2 | A2 | MISO | MISO |
|
||||
| EEM0 | 3 | A3 | CS0 | CS0 |
|
||||
| EEM0 | 4 | A4 | CS1 | CS1 |
|
||||
| EEM0 | 5 | A5 | CS2 | NU_CLK |
|
||||
| EEM0 | 6 | A6 | IO_UPDATE | IO_UPDATE |
|
||||
| EEM0 | 7 | A7 | DDS_RESET, SYNC_IN | SYNC_IN |
|
||||
| EEM1 | 0 | B8 | SYNC_CLK | NU_MOSI0 |
|
||||
| EEM1 | 1 | B9 | SYNC_OUT | NU_MOSI1 |
|
||||
| EEM1 | 2 | B10 | IO_UPDATE_RET | NU_MOSI2 |
|
||||
| EEM1 | 3 | B11 | UNUSED | NU_MOSI3 |
|
||||
| EEM1 | 4 | B12 | SW0 | SW0 |
|
||||
| EEM1 | 5 | B13 | SW1 | SW1 |
|
||||
| EEM1 | 6 | B14 | SW2 | SW2 |
|
||||
| EEM1 | 7 | B15 | SW3 | SW3 |
|
||||
|
||||
IFC_MODE
|
||||
--------
|
||||
|
||||
DIP switches are used to configure the operation of the Urukul CPLD. The
|
||||
four IFC mode switches (the first bank, SW1) are assigned as:
|
||||
|
||||
| IFC_MODE | Name | Function |
|
||||
|----------+---------+-------------------------------------------------|
|
||||
| 0 | EN_9910 | On if AD9910 is populated (OR VARIANT) |
|
||||
| 1 | UNUSED | Unused on Urukul DIOT |
|
||||
| 2 | EN_EEM1 | On if the SYNC signals on EEM1 should be driven |
|
||||
| 3 | UNUSED | Unusable on Urukul/v1.0 |
|
||||
|
||||
On Urukul/v1.0, IFC_MODE[0] | IFC_MODE[3] drive EN_9910.
|
||||
On Urukul/v1.1, IFC_MODE[0] | VARIANT (board population) drive EN_9910.
|
||||
|
||||
Second bank (SW2) of DIP switches is unused.
|
||||
|
||||
See :class:`Urukul`
|
||||
|
||||
SPI
|
||||
---
|
||||
|
||||
An SPI interface is provided to access any of the six serial devices (the
|
||||
configuration/status SPI interface, the attenuator SPI interface, and the
|
||||
four DDS SPI interfaces). It comprises the SCLK, MOSI, MISO, CS0, CS1, and
|
||||
CS2 signals. In the SU-Servo mode, both MISO and CS2 (and the functionality
|
||||
provided by them) are unavailable. I.e. CS >= 4 (individual DDS access)
|
||||
are only available outside of the SU-Servo mode or through CS = 3 (and
|
||||
CFG.MASK_NU).
|
||||
|
||||
The target chip (or set of chips) is selected by CS0/CS1/CS2 (CS2 being the
|
||||
MSB). The encoding is as follows:
|
||||
|
||||
| CS | chip |
|
||||
|-----------+--------------------------------------------|
|
||||
| 0 = 0b000 | None |
|
||||
| 1 = 0b001 | CFG |
|
||||
| 2 = 0b010 | ATT |
|
||||
| 3 = 0b011 | Multiple DDS (those masked by CFG.MASK_NU) |
|
||||
| 4 = 0b100 | DDS0 |
|
||||
| 5 = 0b101 | DDS1 |
|
||||
| 6 = 0b110 | DDS2 |
|
||||
| 7 = 0b111 | DDS3 |
|
||||
|
||||
The SPI interface is CPOL=0, CPHA=0, SPI mode 0, 4-wire, full fuplex. Clock
|
||||
cycles during CS[0:2] = 0 are ignored (but may still be visible on the DDS
|
||||
SCK outputs).
|
||||
|
||||
See :class:`Urukul` and :class:`SR`
|
||||
|
||||
CFG
|
||||
---
|
||||
|
||||
The configuration status register controls the overall operation of Urukul,
|
||||
allows some configuration options to be changed and the status of some
|
||||
signals to be monitored.
|
||||
|
||||
It is 52 bits wide, MSB first.
|
||||
|
||||
See :class:`SR`
|
||||
|
||||
CFG write
|
||||
.........
|
||||
|
||||
See :class:`CFG`
|
||||
|
||||
CFG read
|
||||
........
|
||||
|
||||
See :class:`Status`
|
||||
|
||||
QSPI
|
||||
----
|
||||
|
||||
In the SU-Servo mode, the four DDS are additionally exposed through a
|
||||
quad-SPI write-only interface defined by the signals NU_CLK, NU_CS, and
|
||||
NU_MOSI[0:3].
|
||||
|
||||
Only those DDS which are **not** masked by CFG.MASK_NU can be accessed
|
||||
through the QSPI interface. This allows initial setup and configuration of
|
||||
the DDS individually through the "regular" SPI interface in this mode.
|
||||
|
||||
DDS[0:3].CS is driven by NU_CS (for those DDS not masked)
|
||||
DDS[0:3].SCK is driven by NU_CLK (for those DDS not masked)
|
||||
DDS[0:3].MOSI is driven by NU_MOSI[0:3] (for those DDS not masked)
|
||||
DDS[0:3].MISO is unavailable
|
||||
DDS[0:3].IO_UPDATE is driven by IO_UPDATE (for those DDS not masked)
|
||||
|
||||
See :class:`Urukul`
|
||||
|
||||
ATT
|
||||
---
|
||||
|
||||
The digital step attenuators are daisy-chained (ATT[n].S_OUT driving the
|
||||
next ATT[n+1].S_IN) and form a 32 bit SPI compatible shift register. If the
|
||||
corresponding CFG.ATT_EN bit is set, the data from the attenuator shift
|
||||
register is transferred to the active attenuation register on the
|
||||
de-selection of the attenuators after shifting.
|
||||
|
||||
Clocking
|
||||
--------
|
||||
|
||||
CFG.CLK_SEL selects the clock source for the clock fanout to the DDS.
|
||||
Valid clocking options are:
|
||||
- 0x00: on-board 100MHz oscillator
|
||||
- 0x01: front-panel SMA
|
||||
- 0x02: internal MMCX (hardware version >= 1.3 only)
|
||||
|
||||
For hardware revisions prior to v1.3, 0x00 selects either the on-board
|
||||
oscillator or the MMCX, dependent on component population. In these
|
||||
hardware revisions, the oscillator must be manually powered down to avoid
|
||||
RF leakage through the clock switch.
|
||||
|
||||
If CFG.DIV is 0 the clock division is determined EN_9910. If it is on,
|
||||
the clock to the DDS (from the XCO, the internal MMCX or the external
|
||||
SMA) is divided by 4.
|
||||
|
||||
If CFG.DIV is 1, 2, or 3 it determines the clock divisor (1, 2, 4).
|
||||
|
||||
Synchronization
|
||||
---------------
|
||||
|
||||
IO_UPDATE_RET is provided to determine the round trip time for IO_UPDATE.
|
||||
|
||||
DDS_RESET (not EN_9910) and SYNC_IN (EN_9910) share an EEM signal.
|
||||
DDS_RESET provides a way to deterministically reset all AD9912 DDS SYNC_CLK
|
||||
divider. (https://ez.analog.com/docs/DOC-14472)
|
||||
|
||||
SYNC_IN is an input to the SYNC fanout (input to Urukul, output from the
|
||||
controlling FPGA upstream) to externally and actively synchronize the
|
||||
AD9910 SYNC_CLK dividers. The SYNC fanout can be driven using either
|
||||
SYNC_IN or DDS0.SYNC_OUT (selected by CFG.SYNC_SEL). The SYNC fanout drives
|
||||
all DDSx.SYNC_IN.
|
||||
|
||||
SYNC_CLK and SYNC_OUT are available with EN_9910 & EN_EEM1 to synchronize
|
||||
external logic to the DDS. A round-trip time measurement using
|
||||
IO_UPDATE_RET would need to be performed. SYNC_CLK is sourced from DDS0,
|
||||
while SYNC_OUT is sourced from the SYNC fanout. Both SYNC_CLK and SYNC_OUT
|
||||
are outputs from Urukul, inputs to the controlling upstream FPGA.
|
||||
|
||||
RF switches
|
||||
-----------
|
||||
|
||||
The RF switches are activated with CFG.RF_SW or (logic OR) SW[0:3].
|
||||
EEM1.SW[0:3] provide a high resolution and high-bandwidth port to RF
|
||||
switching.
|
||||
|
||||
LEDs
|
||||
----
|
||||
|
||||
The green channel LEDs mirror the status of the RF switches. The red LEDs
|
||||
are activated by ``CFG.LED | (EN_9910 & (DDS[0:3].SMP_ERR |
|
||||
~DDS[0:3].PLL_LOCK))``. I.e. they are lit by the register or (logic OR) an
|
||||
synchronization/PLL error on that channel's DDS.
|
||||
|
||||
Test points
|
||||
-----------
|
||||
|
||||
The test points expose miscellaneous signals for debugging and are not part
|
||||
of the protocol revision.
|
||||
"""
|
||||
|
||||
def __init__(self, platform, enable_suservo=False):
|
||||
clk = platform.request("clk")
|
||||
dds_sync = platform.request("dds_sync")
|
||||
dds_common = platform.request("dds_common")
|
||||
ifc_mode = platform.request("ifc_mode", 0)
|
||||
variant = platform.request("variant")
|
||||
att = platform.request("att")
|
||||
dds = [platform.request("dds", i) for i in range(4)]
|
||||
|
||||
ts_clk_div = TSTriple()
|
||||
self.specials += [
|
||||
ts_clk_div.get_tristate(clk.div)
|
||||
]
|
||||
|
||||
# Convert LVDS signals from EEM interface
|
||||
self.eem = eem = [platform.request("eem_p", i) for i in range(12)]
|
||||
|
||||
# SPI clock
|
||||
self.clock_domains.cd_sck0 = ClockDomain("sck0", reset_less=True)
|
||||
self.clock_domains.cd_sck1 = ClockDomain("sck1", reset_less=True)
|
||||
|
||||
en_9910 = Signal() # AD9910 populated (instead of AD9912)
|
||||
en_eem1 = Signal() # EEM1 connected and sync outputs used
|
||||
|
||||
miso_phy = Signal()
|
||||
io_update_ret = Signal()
|
||||
|
||||
mosi = Signal()
|
||||
nu_clk = Signal()
|
||||
io_update = Signal()
|
||||
dds_reset = Signal()
|
||||
nu_mosi = Signal(4)
|
||||
|
||||
self.specials += [
|
||||
Instance("SB_GB_IO",
|
||||
p_PIN_TYPE=0b000000, # PIN_TYPE setting for this SB_GB_IO primitive does not actually matter.
|
||||
p_IO_STANDARD="SB_LVDS_INPUT",
|
||||
io_PACKAGE_PIN=eem[0],
|
||||
o_GLOBAL_BUFFER_OUTPUT=self.cd_sck1.clk),
|
||||
DifferentialInput(eem[1], Signal(), mosi),
|
||||
DifferentialOutput(miso_phy, eem[2], platform.request("eem_n", 2)),
|
||||
DifferentialInput(eem[6], Signal(), io_update),
|
||||
DifferentialInput(eem[7], Signal(), dds_reset),
|
||||
]
|
||||
|
||||
platform.add_period_constraint(eem[0], 8.)
|
||||
|
||||
if enable_suservo:
|
||||
self.specials += [
|
||||
DifferentialInput(eem[5], Signal(), nu_clk),
|
||||
*[ DifferentialInput(eem[8 + i], Signal(), nu_mosi[i]) for i in range(4) ]
|
||||
]
|
||||
|
||||
cs = Signal(2)
|
||||
else:
|
||||
self.specials += [
|
||||
Instance("SB_IO",
|
||||
p_PIN_TYPE=C(0b101000, 6),
|
||||
p_IO_STANDARD="SB_LVCMOS",
|
||||
io_PACKAGE_PIN=eem[10],
|
||||
i_OUTPUT_ENABLE=en_eem1,
|
||||
i_D_OUT_0=io_update_ret),
|
||||
Instance("SB_IO",
|
||||
p_PIN_TYPE=C(0b101000, 6),
|
||||
p_IO_STANDARD="SB_LVCMOS",
|
||||
io_PACKAGE_PIN=platform.request("eem_n", 10),
|
||||
i_OUTPUT_ENABLE=en_eem1,
|
||||
i_D_OUT_0=~io_update_ret),
|
||||
]
|
||||
|
||||
cs = Signal(3)
|
||||
|
||||
for i, csi in enumerate(cs):
|
||||
self.specials += DifferentialInput(eem[i + 3], Signal(), cs[i])
|
||||
|
||||
self.comb += [
|
||||
en_9910.eq(ifc_mode[0] | variant),
|
||||
en_eem1.eq(ifc_mode[2]),
|
||||
io_update_ret.eq(io_update),
|
||||
self.cd_sck0.clk.eq(~self.cd_sck1.clk),
|
||||
dds_sync.clk_out_en.eq((not enable_suservo) & en_eem1 & en_9910),
|
||||
dds_sync.sync_out_en.eq((not enable_suservo) & en_eem1 & en_9910),
|
||||
]
|
||||
|
||||
cfg = CFG(platform)
|
||||
stat = Status(platform)
|
||||
sr = SR(52)
|
||||
assert len(cfg.data) <= len(sr.di)
|
||||
assert len(stat.data) <= len(sr.do)
|
||||
self.submodules += cfg, stat, sr
|
||||
|
||||
sel = Signal(8)
|
||||
miso = Signal(8)
|
||||
|
||||
self.specials += [
|
||||
Instance("SB_DFFES", i_D=0, i_C=ClockSignal("sck1"), i_E=sel[2],
|
||||
i_S=~(sel[2] & cfg.data.att_en[i]), o_Q=att.le[i]) for i in range(4)
|
||||
]
|
||||
|
||||
if enable_suservo:
|
||||
dds_miso_sel = Signal(max=len(cfg.data.mask_nu))
|
||||
self.submodules.dds_miso_encoder = Encoder(len(cfg.data.mask_nu))
|
||||
|
||||
self.comb += [
|
||||
self.dds_miso_encoder.i.eq(cfg.data.mask_nu),
|
||||
dds_miso_sel.eq(Mux(self.dds_miso_encoder.n,
|
||||
0, self.dds_miso_encoder.o)),
|
||||
|
||||
miso[3].eq(Array(miso[4:])[dds_miso_sel]),
|
||||
]
|
||||
|
||||
self.comb += [
|
||||
cfg.en_9910.eq(en_9910),
|
||||
Array(sel)[cs].eq(1), # one-hot
|
||||
miso_phy.eq(Array(miso)[cs]),
|
||||
|
||||
att.clk.eq(sel[2] & self.cd_sck1.clk),
|
||||
Cat(att.s_in, miso[2]).eq(Cat(mosi, att.s_out)),
|
||||
|
||||
sr.sel.eq(sel[1]),
|
||||
sr.sdi.eq(mosi),
|
||||
miso[1].eq(sr.sdo),
|
||||
|
||||
cfg.data.raw_bits().eq(sr.di),
|
||||
sr.do.eq(stat.data.raw_bits()),
|
||||
|
||||
# dividers: z: 1, 0: 2, 1: 4
|
||||
# 1: div-by-4 for AD9910
|
||||
# z: div-by-1 for AD9912
|
||||
ts_clk_div.oe.eq(Array([en_9910, 0, 1, 1])[cfg.data.div]),
|
||||
ts_clk_div.o.eq(Array([1, 1, 0, 1])[cfg.data.div]),
|
||||
]
|
||||
for i, ddsi in enumerate(dds):
|
||||
sel_spi = Signal()
|
||||
sel_nu = Signal()
|
||||
self.comb += [
|
||||
sel_spi.eq(sel[i + 4] | (sel[3] & cfg.data.mask_nu[i])),
|
||||
sel_nu.eq(enable_suservo & ~cfg.data.mask_nu[i]),
|
||||
ddsi.sdi.eq(Mux(sel_nu, nu_mosi[i], mosi)),
|
||||
# NU_CLK exclusively drive SCLK in SU-Servo mode.
|
||||
# As per the AD9910 datasheet, CS_N can be tied low.
|
||||
ddsi.cs_n.eq(~Mux(sel_nu, 1, sel_spi)),
|
||||
ddsi.sck.eq(Mux(sel_nu, nu_clk, self.cd_sck1.clk)),
|
||||
miso[i + 4].eq(ddsi.sdo),
|
||||
ddsi.io_update.eq(Mux(cfg.data.mask_nu[i],
|
||||
cfg.data.io_update[i], io_update)),
|
||||
ddsi.reset.eq(cfg.data.rst | dds_reset),
|
||||
]
|
||||
|
||||
tp = [platform.request("tp", i) for i in range(5)]
|
||||
self.comb += [
|
||||
tp[0].eq(dds[0].cs_n),
|
||||
tp[1].eq(dds[0].sck),
|
||||
tp[2].eq(dds[0].sdi),
|
||||
tp[3].eq(dds[0].sdo),
|
||||
tp[4].eq(dds[0].drover),
|
||||
]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Urukul binary builder for the iCE40 variant")
|
||||
# iCE40 does not support bi-directional LVDS I/O.
|
||||
# Hence, Urukuls in the SU-Servo mode use a different binary.
|
||||
# https://www.latticesemi.com/support/answerdatabase/3/8/8/3881
|
||||
parser.add_argument("--suservo", action="store_true",
|
||||
help="enable SU-Servo support")
|
||||
args = parser.parse_args()
|
||||
|
||||
from platform import Platform
|
||||
platform = Platform()
|
||||
platform.toolchain.nextpnr_build_template[1:2] = [
|
||||
"for seed in `seq 1 10`; do",
|
||||
"echo Seed $seed",
|
||||
("nextpnr-ice40 {pnr_pkg_opts} --pcf {build_name}.pcf --json {build_name}.json "
|
||||
"--asc {build_name}.txt --pre-pack {build_name}_pre_pack.py "
|
||||
"--no-promote-globals --seed $seed && break"),
|
||||
"done"
|
||||
]
|
||||
urukul = Urukul(platform, args.suservo)
|
||||
platform.build(urukul, build_name="suservo" if args.suservo else "urukul")
|
||||
@@ -8,7 +8,7 @@ test:
|
||||
.PHONY: build
|
||||
build: build/urukul.vm6
|
||||
|
||||
build/urukul.vm6: urukul.py urukul_cpld.py
|
||||
build/urukul.vm6: urukul.py platform.py
|
||||
python urukul_impl.py
|
||||
|
||||
REV:=$(shell git describe --always --abbrev=8 --dirty)
|
||||
@@ -1,4 +1,5 @@
|
||||
from migen import *
|
||||
from migen.genlib.coding import Encoder
|
||||
|
||||
|
||||
# increment this if the behavior (LEDs, registers, EEM pins) changes
|
||||
@@ -226,10 +227,10 @@ class Urukul(Module):
|
||||
|------+-----------+---------+-------------------------|
|
||||
| EEM0 | 0 | A0 | SCLK |
|
||||
| EEM0 | 1 | A1 | MOSI |
|
||||
| EEM0 | 2 | A2 | MISO, NU_CLK |
|
||||
| EEM0 | 2 | A2 | MISO |
|
||||
| EEM0 | 3 | A3 | CS0 |
|
||||
| EEM0 | 4 | A4 | CS1 |
|
||||
| EEM0 | 5 | A5 | CS2, NU_CS |
|
||||
| EEM0 | 5 | A5 | CS2, NU_CLK |
|
||||
| EEM0 | 6 | A6 | IO_UPDATE |
|
||||
| EEM0 | 7 | A7 | DDS_RESET, SYNC_OUT |
|
||||
| EEM1 | 0 | B8 | SYNC_CLK, NU_MOSI0 |
|
||||
@@ -428,7 +429,8 @@ class Urukul(Module):
|
||||
self.clock_domains.cd_sck1 = ClockDomain("sck1", reset_less=True)
|
||||
|
||||
platform.add_period_constraint(eem[0]._pin, 8.)
|
||||
platform.add_period_constraint(eem[2]._pin, 8.)
|
||||
# platform.add_period_constraint(eem[2]._pin, 8.)
|
||||
platform.add_period_constraint(eem[5]._pin, 8.)
|
||||
|
||||
self.specials += [
|
||||
Instance("BUFG", i_I=eem[0].i, o_O=self.cd_sck1.clk),
|
||||
@@ -444,7 +446,7 @@ class Urukul(Module):
|
||||
en_nu.eq(ifc_mode[1]),
|
||||
en_eem1.eq(ifc_mode[2]),
|
||||
[eem[i].oe.eq(0) for i in range(12) if i not in (2, 10)],
|
||||
eem[2].oe.eq(~en_nu),
|
||||
eem[2].oe.eq(1),
|
||||
eem[10].oe.eq(~en_nu & en_eem1),
|
||||
eem[10].o.eq(eem[6].i),
|
||||
self.cd_sck0.clk.eq(~self.cd_sck1.clk),
|
||||
@@ -462,12 +464,16 @@ class Urukul(Module):
|
||||
sel = Signal(8)
|
||||
cs = Signal(3)
|
||||
miso = Signal(8)
|
||||
self.comb += miso[0].eq(0)
|
||||
mosi = eem[1].i
|
||||
|
||||
self.specials += [Instance("FDPE", p_INIT=1,
|
||||
i_D=0, i_C=ClockSignal("sck1"), i_CE=sel[2],
|
||||
i_PRE=~(sel[2] & cfg.data.att_en[i]), o_Q=att.le[i]) for i in range(4)]
|
||||
|
||||
dds_miso_sel = Signal(max=len(cfg.data.mask_nu))
|
||||
self.submodules.dds_miso_encoder = Encoder(len(cfg.data.mask_nu))
|
||||
|
||||
self.comb += [
|
||||
cfg.en_9910.eq(en_9910),
|
||||
# Important note regarding the chip-select (CS) signals (`eem[3].i`, `eem[4].i`, `eem[5].i`):
|
||||
@@ -488,7 +494,11 @@ class Urukul(Module):
|
||||
|
||||
Array(sel)[cs].eq(1), # one-hot
|
||||
eem[2].o.eq(Array(miso)[cs]),
|
||||
miso[3].eq(miso[4]), # for all-DDS take DDS0:MISO
|
||||
|
||||
self.dds_miso_encoder.i.eq(cfg.data.mask_nu),
|
||||
dds_miso_sel.eq(Mux(self.dds_miso_encoder.n,
|
||||
0, self.dds_miso_encoder.o)),
|
||||
miso[3].eq(Array(miso[4:])[dds_miso_sel]),
|
||||
|
||||
att.clk.eq(sel[2] & self.cd_sck1.clk),
|
||||
Cat(att.s_in, miso[2]).eq(Cat(mosi, att.s_out)),
|
||||
@@ -512,8 +522,10 @@ class Urukul(Module):
|
||||
self.comb += [
|
||||
sel_spi.eq(sel[i + 4] | (sel[3] & cfg.data.mask_nu[i])),
|
||||
sel_nu.eq(en_nu & ~cfg.data.mask_nu[i]),
|
||||
ddsi.cs_n.eq(~Mux(sel_nu, eem[5].i, sel_spi)),
|
||||
ddsi.sck.eq(Mux(sel_nu, eem[2].i, self.cd_sck1.clk)),
|
||||
# NU_CLK exclusively drive SCLK in SU-Servo mode.
|
||||
# As per the AD9910 datasheet, CS_N can be tied low.
|
||||
ddsi.cs_n.eq(~Mux(sel_nu, 1, sel_spi)),
|
||||
ddsi.sck.eq(Mux(sel_nu, eem[5].i, self.cd_sck1.clk)),
|
||||
ddsi.sdi.eq(Mux(sel_nu, eem[i + 8].i, mosi)),
|
||||
miso[i + 4].eq(ddsi.sdo),
|
||||
ddsi.io_update.eq(Mux(cfg.data.mask_nu[i],
|
||||
@@ -1,5 +1,5 @@
|
||||
def main():
|
||||
from urukul_cpld import Platform
|
||||
from platform import Platform
|
||||
from urukul import Urukul
|
||||
|
||||
p = Platform()
|
||||
Reference in New Issue
Block a user