Skip to content

Commit 482ba75

Browse files
committed
Initial commit with READ working
1 parent c9b319f commit 482ba75

12 files changed

+5018
-0
lines changed

.vscode/c_cpp_properties.json

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"configurations": [
3+
{
4+
"name": "Win32",
5+
"includePath": [
6+
"${workspaceFolder}/**"
7+
],
8+
"defines": [
9+
"_DEBUG",
10+
"UNICODE",
11+
"_UNICODE"
12+
],
13+
"cStandard": "c17",
14+
"cppStandard": "c++17",
15+
"intelliSenseMode": "windows-msvc-x64"
16+
}
17+
],
18+
"version": 4
19+
}

.vscode/settings.json

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"python.pythonPath": "C:\\Users\\Murmann-Group\\anaconda3\\envs\\NI-RRAM-Python\\python.exe",
3+
"python.linting.pylintPath": "C:\\Users\\Murmann-Group\\anaconda3\\envs\\NI-RRAM-Python\\Scripts\\pylint.exe",
4+
"python.linting.enabled": true,
5+
"python.linting.lintOnSave" : true,
6+
"python.linting.pylintUseMinimalCheckers": false,
7+
"C_Cpp.errorSquiggles": "Disabled",
8+
}

ni_hsdio.py

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
"""Provides Python wrapper for NI-HSDIO C library DLL. Currently implements static generation."""
2+
from ctypes import CDLL, c_int, c_uint, create_string_buffer, pointer
3+
4+
5+
class NIHSDIOException(Exception):
6+
"""Exception produced by the NIHSDIO class"""
7+
def __init__(self, msg):
8+
super().__init__(f"NIHSDIO: {msg}")
9+
10+
11+
class NIHSDIO:
12+
"""A wrapper class for NI-HSDIO C DLL. Also includes functions for static assignment."""
13+
14+
# Logic family constants
15+
NIHSDIO_VAL_5_0V_LOGIC = 5
16+
NIHSDIO_VAL_3_3V_LOGIC = 6
17+
NIHSDIO_VAL_2_5V_LOGIC = 7
18+
NIHSDIO_VAL_1_8V_LOGIC = 8
19+
NIHSDIO_VAL_1_5V_LOGIC = 80
20+
NIHSDIO_VAL_1_2V_LOGIC = 81
21+
22+
def __init__(self, deviceID, chanMap=None, channelList="", logicFamily=NIHSDIO_VAL_1_8V_LOGIC):
23+
# Store parameters
24+
self.device_id = deviceID
25+
self.chans = channelList
26+
self.chan_map = chanMap if chanMap is not None else {}
27+
28+
# Create driver and VI session
29+
self.driver = CDLL("lib/niHSDIO_64.dll")
30+
self.sess = c_uint(0)
31+
32+
# Initialize generation session
33+
self.init_generation_session(deviceID)
34+
35+
# Configure voltage level
36+
self.configure_data_voltage_logic_family(channelList, logicFamily)
37+
38+
# Assign static channels
39+
self.assign_static_channels(channelList)
40+
41+
def init_generation_session(self, device_id):
42+
"""Wrapper function: initialize HSDIO session for digital pattern generation"""
43+
# Create session
44+
device_id = bytes(device_id, 'utf-8')
45+
err = self.driver.niHSDIO_InitGenerationSession(device_id, 0, 0, None, pointer(self.sess))
46+
self.check_err(err)
47+
48+
def configure_data_voltage_logic_family(self, chans="", logic_family=NIHSDIO_VAL_1_8V_LOGIC):
49+
"""Wrapper function: configure data voltage logic family"""
50+
err = self.driver.niHSDIO_ConfigureDataVoltageLogicFamily(self.sess, chans, logic_family)
51+
self.check_err(err)
52+
53+
def assign_static_channels(self, chans=""):
54+
"""Wrapper function: configure channels for static digital generation"""
55+
# Create session
56+
err = self.driver.niHSDIO_AssignStaticChannels(self.sess, chans)
57+
self.check_err(err)
58+
59+
def write_static(self, write_data, channel_mask=0xFFFFFFFF):
60+
"""Wrapper function: write static data to configured channels"""
61+
err = self.driver.niHSDIO_WriteStaticU32(self.sess, write_data, channel_mask)
62+
self.check_err(err)
63+
64+
def check_err(self, err):
65+
"""Wrapper function: given an error code, return error description"""
66+
if err != 0:
67+
err_desc = create_string_buffer(1024)
68+
self.driver.niHSDIO_GetError(None, pointer(c_int(err)), 1024, err_desc)
69+
raise NIHSDIOException(err_desc.value)
70+
71+
def close(self):
72+
"""Wrapper function: close NI-HSDIO session"""
73+
self.check_err(self.driver.niHSDIO_close(self.sess))
74+
75+
def write_data_across_chans(self, chans, data, debug=False):
76+
"""Writes a pattern across channels. If chans is a string, it is interpreted as a
77+
channel map key. If chans is a list, it will be directly interpreted as the channels.
78+
data will be interpreted as an unsigned 32-bit integer."""
79+
# Type checking and conversion
80+
if isinstance(chans, str):
81+
chans = self.chan_map[chans]
82+
if not isinstance(chans, list):
83+
err = f"Channels must be specified as (or mapped to) a list: got {repr(chans)}."
84+
raise NIHSDIOException(err)
85+
86+
# Convert data to binary string for bit access
87+
data = format(data, '032b')[::-1]
88+
89+
# Reorder bits for static generation
90+
write_data = 0
91+
mask = 0
92+
for chan, bit in zip(chans, data):
93+
mask |= (1 << chan)
94+
write_data |= (int(bit) << chan)
95+
96+
# Debug statements
97+
if debug:
98+
print("Write data:", write_data)
99+
print("Mask:", mask)
100+
print("Original data binary:", data[::-1])
101+
print("Write data binary:", format(write_data, '032b'))
102+
print("Mask binary:", format(mask, '032b'))
103+
print()
104+
105+
# Write data using driver
106+
self.write_static(write_data, mask)
107+
108+
def __del__(self):
109+
# Try to close session on deletion
110+
try:
111+
self.close()
112+
except NIHSDIOException:
113+
pass
114+
115+
if __name__=="__main__":
116+
# Initialize and run basic static generation tests
117+
nihsdio = NIHSDIO("Dev7", {"wl_addr": [1,3,5,7,9,11]})
118+
nihsdio.write_static(0xFFFFFFFF)
119+
nihsdio.write_data_across_chans("wl_addr", 10, debug=True)
120+
nihsdio.write_data_across_chans([13,15,30,28,26,24,22], 10, debug=True)

ni_rram.py

+220
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
"""Defines the NI RRAM controller class"""
2+
import json
3+
import time
4+
import warnings
5+
import nidaqmx
6+
import nidcpower
7+
import nifgen
8+
import numpy as np
9+
from ni_hsdio import NIHSDIO
10+
11+
warnings.filterwarnings("error")
12+
13+
def accurate_delay(delay):
14+
"""Function to provide accurate time delay"""
15+
_ = time.perf_counter() + delay
16+
while time.perf_counter() < _:
17+
pass
18+
19+
20+
class NIRRAMException(Exception):
21+
"""Exception produced by the NIRRAM class"""
22+
def __init__(self, msg):
23+
super().__init__(f"NIRRAM: {msg}")
24+
25+
26+
class NIRRAM:
27+
"""The NI RRAM controller class that controls the instrument drivers."""
28+
def __init__(self, chip, settings="settings.json"):
29+
# If settings is a string, load as JSON file
30+
if isinstance(settings, str):
31+
with open(settings) as settings_file:
32+
settings = json.load(settings_file)
33+
34+
# Ensure settings is a dict
35+
if not isinstance(settings, dict):
36+
raise NIRRAMException(f"Settings should be a dict, got {repr(settings)}.")
37+
38+
# TODO: initialize logger
39+
40+
# Store/initialize parameters
41+
self.chip = chip
42+
self.settings = settings
43+
self.addr = 0
44+
self.prof = {"READs": 0, "SETs": 0, "RESETs": 0}
45+
46+
# Initialize NI-HSDIO driver
47+
self.hsdio = NIHSDIO(**settings["HSDIO"])
48+
49+
# Initialize NI-DAQmx driver for READ voltage
50+
self.read_chan = nidaqmx.Task()
51+
self.read_chan.ai_channels.add_ai_voltage_chan(settings["DAQmx"]["chanMap"]["read_ai"])
52+
self.read_chan.timing.cfg_samp_clk_timing(settings["READ"]["read_clk_rate"])
53+
54+
# Initialize NI-DAQmx driver for WL voltages
55+
self.wl_ext_chans = []
56+
for chan in settings["DAQmx"]["chanMap"]["wl_ext"]:
57+
task = nidaqmx.Task()
58+
task.ao_channels.add_ao_voltage_chan(chan)
59+
task.timing.cfg_samp_clk_timing(settings["samp_clk_rate"])
60+
task.start()
61+
self.wl_ext_chans.append(task)
62+
63+
# Initialize NI-DCPower driver for BL voltages
64+
self.bl_ext_chans = []
65+
for chan in settings["DCPower"]["chans"]:
66+
sess = nidcpower.Session(settings["DCPower"]["deviceID"], chan)
67+
sess.voltage_level = 0
68+
sess.commit()
69+
sess.initiate()
70+
self.bl_ext_chans.append(sess)
71+
72+
# Initialize NI-FGen driver for SL voltage
73+
self.sl_ext_chan = nifgen.Session(settings["FGen"]["deviceID"])
74+
self.sl_ext_chan.output_mode = nifgen.OutputMode.FUNC
75+
self.sl_ext_chan.configure_standard_waveform(nifgen.Waveform.DC, 0.0, frequency=10000000)
76+
self.sl_ext_chan.initiate()
77+
78+
# Set address to 0
79+
self.set_address(0)
80+
81+
def read(self):
82+
"""Perform a READ operation. Returns tuple with (res, cond, meas_i, meas_v)"""
83+
# Increment the number of READs
84+
self.prof["READs"] += 1
85+
86+
# Address decoder enable
87+
self.decoder_enable()
88+
89+
# Set voltages
90+
self.set_vsl(0)
91+
self.set_vbl(self.settings["READ"]["VBL"])
92+
self.set_vwl(self.settings["READ"]["VWL"])
93+
94+
# Settling time for VBL
95+
accurate_delay(self.settings["READ"]["settling_time"])
96+
97+
# Measure
98+
meas_v = np.mean(self.read_chan.read(self.settings["READ"]["n_samples"]))
99+
meas_i = meas_v/self.settings["READ"]["shunt_res_value"]
100+
res = np.abs(self.settings["READ"]["VBL"]/meas_i - self.settings["READ"]["shunt_res_value"])
101+
cond = 1/res
102+
103+
# Turn off VBL and VWL
104+
self.set_vbl(0)
105+
self.set_vwl(0)
106+
107+
# Address decoder disable
108+
self.decoder_disable()
109+
110+
# TODO: log READ operation
111+
112+
# Return measurement tuple
113+
return res, cond, meas_i, meas_v
114+
115+
def set_pulse(self, vwl=None, vbl=None, pw=None):
116+
117+
118+
def set_vsl(self, voltage):
119+
"""Set VSL using NI-FGen driver"""
120+
# Set DC offset to V/2 since it is doubled by FGen for some reason
121+
self.sl_ext_chan.func_dc_offset = voltage/2
122+
123+
def set_vbl(self, voltage):
124+
"""Set (active) VBL using NI-DCPower driver (inactive disabled)"""
125+
# LSB indicates active BL channel
126+
active_bl_chan = (self.addr >> 0) & 0b1
127+
active_bl = self.bl_ext_chans[active_bl_chan]
128+
inactive_bl = self.bl_ext_chans[1-active_bl_chan]
129+
130+
# Set voltages and commit
131+
active_bl.voltage_level = voltage
132+
active_bl.commit()
133+
inactive_bl.voltage_level = 0
134+
inactive_bl.commit()
135+
136+
def set_vwl(self, voltage):
137+
"""Set (active) VWL using NI-DAQmx driver (inactive disabled)"""
138+
# Select 8th and 9th bit to get the channel and the driver card, respectively
139+
active_wl_chan = (self.addr >> 8) & 0b1
140+
active_wl_dev = (self.addr >> 9) & 0b1
141+
active_wl = self.wl_ext_chans[active_wl_dev]
142+
inactive_wl = self.wl_ext_chans[1-active_wl_dev]
143+
144+
# Write
145+
active_wl.write([(1-active_wl_chan)*voltage, active_wl_chan*voltage], auto_start=True)
146+
inactive_wl.write([0,0], auto_start=True)
147+
148+
def pulse_vwl(self, voltage, pw):
149+
"""Pulse (active) VWL using NI-DAQmx driver (inactive disabled)"""
150+
# Select 8th and 9th bit to get the channel and the driver card, respectively
151+
active_wl_chan = (self.addr >> 8) & 0b1
152+
active_wl_dev = (self.addr >> 9) & 0b1
153+
active_wl = self.wl_ext_chans[active_wl_dev]
154+
inactive_wl = self.wl_ext_chans[1-active_wl_dev]
155+
156+
# Write
157+
active_wl.write([(1-active_wl_chan)*voltage, active_wl_chan*voltage], auto_start=True)
158+
inactive_wl.write([0,0], auto_start=True)
159+
160+
def decoder_enable(self):
161+
"""Enable decoding circuitry using digital signals"""
162+
self.hsdio.write_data_across_chans("wl_dec_en", 0b11)
163+
self.hsdio.write_data_across_chans("sl_dec_en", 0b1)
164+
self.hsdio.write_data_across_chans("wl_clk", 0b1)
165+
166+
def decoder_disable(self):
167+
"""Disable decoding circuitry using digital signals"""
168+
self.hsdio.write_data_across_chans("wl_dec_en", 0b00)
169+
self.hsdio.write_data_across_chans("sl_dec_en", 0b0)
170+
self.hsdio.write_data_across_chans("wl_clk", 0b0)
171+
172+
def set_address(self, addr):
173+
"""Set the address and hold briefly"""
174+
# Update address
175+
self.addr = addr
176+
177+
# Extract SL and WL addresses
178+
sl_addr = (self.addr >> 1) & 0b1111111
179+
wl_addr = (self.addr >> 10) & 0b111111
180+
181+
# Write addresses to corresponding HSDIO channels
182+
self.hsdio.write_data_across_chans("sl_addr", sl_addr)
183+
self.hsdio.write_data_across_chans("wl_addr", wl_addr)
184+
accurate_delay(self.settings["addr_hold_time"])
185+
186+
# Reset profiling counters
187+
self.prof = {"READs": 0, "SETs": 0, "RESETs": 0}
188+
189+
def close(self):
190+
"""Close all NI sessions"""
191+
# Close NI-HSDIO
192+
self.hsdio.close()
193+
194+
# Close NI-DAQmx AI
195+
self.read_chan.close()
196+
197+
# Close NI-DAQmx AOs
198+
for task in self.wl_ext_chans:
199+
task.stop()
200+
task.close()
201+
202+
# Close NI-DCPower
203+
for sess in self.bl_ext_chans:
204+
sess.abort()
205+
sess.close()
206+
207+
# Close NI-FGen
208+
self.sl_ext_chan.abort()
209+
self.sl_ext_chan.close()
210+
211+
# Give some time to shutdown
212+
time.sleep(1)
213+
214+
if __name__=="__main__":
215+
# Basic test
216+
nirram = NIRRAM("Chip9")
217+
for address in range(2000):
218+
nirram.set_address(address)
219+
if address % 100 == 0:
220+
print((address, nirram.read()),)

0 commit comments

Comments
 (0)