| 1 |
9 |
sinclairrf |
################################################################################
|
| 2 |
|
|
#
|
| 3 |
|
|
# Copyright 2015, Sinclair R.F., Inc.
|
| 4 |
|
|
#
|
| 5 |
|
|
################################################################################
|
| 6 |
|
|
|
| 7 |
|
|
import math
|
| 8 |
|
|
|
| 9 |
|
|
from ssbccPeripheral import SSBCCperipheral
|
| 10 |
|
|
from ssbccUtil import CeilLog2
|
| 11 |
|
|
from ssbccUtil import SSBCCException
|
| 12 |
|
|
|
| 13 |
|
|
class stepper_motor(SSBCCperipheral):
|
| 14 |
|
|
"""
|
| 15 |
|
|
Stepper motor driver\n
|
| 16 |
|
|
This peripheral creates pulses to driver a stepper motor driver such as TI's
|
| 17 |
|
|
DRV8825. It includes a buffer which can be used to store acceleration,
|
| 18 |
|
|
motion, and deceleration profiles and returns a "completed" status to the
|
| 19 |
|
|
micro controller.\n
|
| 20 |
|
|
The core runs accumulators for the angle and the rate. I.e. the rate is an
|
| 21 |
|
|
accumulated sum of the initial rate and the commanded acceleration and the
|
| 22 |
11 |
sinclairrf |
angle is an accumulation of this possibly changing rate. The direction
|
| 23 |
|
|
signal (if present) to the stepper motor is the sign bit from the accumulated
|
| 24 |
|
|
rate. The "step" signal to the stepper motor driver is strobed every time
|
| 25 |
|
|
the accumulated angle overflows or underflows and the direction bit to the
|
| 26 |
|
|
driver is set according to whether the accumulated angle overflowed or
|
| 27 |
|
|
underflowed.\n
|
| 28 |
9 |
sinclairrf |
The motor control word consists of the following signals:
|
| 29 |
|
|
initial rate
|
| 30 |
|
|
acceleration
|
| 31 |
|
|
number of steps to be performed this control word
|
| 32 |
|
|
optional mode specification
|
| 33 |
|
|
These control words are individually packed into one or more 8-bit bytes. The
|
| 34 |
|
|
8-bit values from the micro controller are shifted into a buffer from which
|
| 35 |
|
|
the control word is constructed. This completed control word is then shifted
|
| 36 |
|
|
into a FIFO. The control words in the FIFO are then used to generate the
|
| 37 |
|
|
timing for the step strobes to the stepper motor driver. When the FIFO
|
| 38 |
|
|
empties, the rate and the acceleration are set to zero and the mode retains
|
| 39 |
|
|
its most recent value. The user must ensure they do not overfill the FIFO --
|
| 40 |
|
|
the only FIFO status is an "empty" or control words "done" status.\n
|
| 41 |
|
|
Usage:
|
| 42 |
|
|
PERIPHERAL stepper_motor basename=name \\
|
| 43 |
|
|
outcontrol=O_name \\
|
| 44 |
|
|
outrecord=O_name \\
|
| 45 |
|
|
outrun=O_name \\
|
| 46 |
|
|
indone=I_name \\
|
| 47 |
11 |
sinclairrf |
[inerror=I_name] \\
|
| 48 |
|
|
[nodir] \\
|
| 49 |
9 |
sinclairrf |
ratemethod={CLK_FREQ_HZ/RATE_HZ|count} \\
|
| 50 |
|
|
ratescale=N_rate_scale \\
|
| 51 |
|
|
rateres=N_rate \\
|
| 52 |
|
|
accelscale=N_accel_scale \\
|
| 53 |
|
|
accelres=N_accel \\
|
| 54 |
|
|
[accumres=N_accum] \\
|
| 55 |
|
|
countwidth=N_count \\
|
| 56 |
|
|
[modewidth=N_mode] \\
|
| 57 |
|
|
[FIFO=N_fifo]\n
|
| 58 |
|
|
Or:\n
|
| 59 |
|
|
PERIPHERAL stepper_motor basename=name \\
|
| 60 |
|
|
master=mastername \\
|
| 61 |
|
|
outrecord=O_name \\
|
| 62 |
|
|
outrun=O_name \\
|
| 63 |
|
|
indone=I_name \\
|
| 64 |
11 |
sinclairrf |
[nodir] \\
|
| 65 |
|
|
[inerror=I_name] \\
|
| 66 |
9 |
sinclairrf |
[FIFO=N_fifo]\n
|
| 67 |
|
|
Where:
|
| 68 |
|
|
basename=name
|
| 69 |
|
|
specifies the name used to contruct the I/O signals
|
| 70 |
|
|
Note: The name must start with an alphabetic character.
|
| 71 |
|
|
Example: "basename=stepper" results in the names "o_stepper_dir",
|
| 72 |
|
|
"o_stepper_step", and "o_stepper_mode" for the output
|
| 73 |
|
|
direction, step, and optional mode signals and
|
| 74 |
|
|
"i_stepper_error" for the input error signal.
|
| 75 |
|
|
master=mastername
|
| 76 |
|
|
specifies a preceding stepper_motor peripheral to use for the internal
|
| 77 |
|
|
clock and to use for the accleration, rate, angle accumulator, and mode
|
| 78 |
|
|
sizes
|
| 79 |
10 |
sinclairrf |
Note: The "outcontrol" port from the master peripheral is used to queue
|
| 80 |
|
|
the control words for its slaves.
|
| 81 |
9 |
sinclairrf |
outcontrol=O_name
|
| 82 |
|
|
specifies the port used to assemble 8-bit control values into the stepper
|
| 83 |
|
|
motor control word
|
| 84 |
|
|
Note: The name must start with "O_".
|
| 85 |
|
|
outrecord=O_name
|
| 86 |
|
|
specifies the port used to generate the strobe that pushes the assembled
|
| 87 |
|
|
motor control word into the stepper motor FIFO
|
| 88 |
|
|
Note: The name must start with "O_".
|
| 89 |
|
|
outrun=O_name
|
| 90 |
|
|
specified the port used to begin the sequence of operations specified by
|
| 91 |
|
|
the motor control words in the buffer
|
| 92 |
|
|
Note: The name must start with "O_".
|
| 93 |
|
|
indone=I_name
|
| 94 |
|
|
specifies the port used to determine whether or not the operations in the
|
| 95 |
|
|
FIFO have finished
|
| 96 |
|
|
Note: The name must start with "I_".
|
| 97 |
|
|
inerror=I_name
|
| 98 |
11 |
sinclairrf |
optionally specifies the port used to read the error status from the
|
| 99 |
|
|
stepper motor controller
|
| 100 |
9 |
sinclairrf |
Note: The name must start with "I_".
|
| 101 |
11 |
sinclairrf |
nodir
|
| 102 |
|
|
optionally specify that the stepper motor does not generate a direction
|
| 103 |
|
|
bit
|
| 104 |
9 |
sinclairrf |
ratemethod
|
| 105 |
|
|
specified the method to generate the internal clock rate from the
|
| 106 |
|
|
processor clock
|
| 107 |
|
|
1st method: CLK_FREQ_HZ/RATE_HZ
|
| 108 |
|
|
CLK_FREQ_HZ is the frequency of "i_clk" in Hz
|
| 109 |
|
|
a number will be interpreted as the clock frequency in Hz
|
| 110 |
|
|
a symbol will be interpreted as a constant or a parameter
|
| 111 |
|
|
Note: the symbol must be declared with the CONSTANT, LOCALPARARM,
|
| 112 |
|
|
or PARAMETER configuration command.
|
| 113 |
|
|
RATE_HZ is the desired internal clock rate
|
| 114 |
|
|
this is specified as per "CLK_FREQ_HZ"
|
| 115 |
|
|
2nd method: count
|
| 116 |
|
|
specify the number of "i_clk" clock cycles per internal clock cycle
|
| 117 |
|
|
Note: CLK_FREQ_HZ, RATE_HZ, and count can be parameters or constants. For example,
|
| 118 |
|
|
the following uses the parameter C_CLK_FREQ_HZ for the clock
|
| 119 |
|
|
frequency and an internal rate to 500 kHz:
|
| 120 |
|
|
"ratemethod=C_CLK_FREQ_HZ/500_000".
|
| 121 |
|
|
Note: The minimum value of "ratemethod" is 2.
|
| 122 |
|
|
ratescale=N_rate_scale
|
| 123 |
|
|
specifies the scaling for the most significant bit of the rate
|
| 124 |
|
|
Note: See the 'a' parameter in the "Theory of Operation" section.
|
| 125 |
|
|
rateres=N_rate
|
| 126 |
|
|
specifies the resolution of the rate
|
| 127 |
|
|
Note: See the 'r' parameter in the "Theory of Operation" section.
|
| 128 |
|
|
accelscale=N_accel_scale
|
| 129 |
|
|
specifies the scaling for the most significant bit of the acceleration
|
| 130 |
|
|
Note: See the 'a' parameter in the "Theory of Operation" section.
|
| 131 |
|
|
accelres=N_accel
|
| 132 |
|
|
specifies the resolution for the acceleration
|
| 133 |
|
|
Note: See the 'b' parameter in the "Theory of Operation" section.
|
| 134 |
|
|
accumres=N_accum
|
| 135 |
|
|
optionally specify the resolution for the accumulator to the summed angle
|
| 136 |
|
|
(from which the step strobes are generated)
|
| 137 |
|
|
Note: This must be between rateres and accelres.
|
| 138 |
|
|
Note: The default value is accelres.
|
| 139 |
|
|
countwidth=N_count
|
| 140 |
|
|
specifies the width of the counter for the number of steps to be performed
|
| 141 |
|
|
by the control word
|
| 142 |
|
|
modewidth=N_mode
|
| 143 |
|
|
- if not specified, there is no mode signal to the stepper motor
|
| 144 |
|
|
controller
|
| 145 |
|
|
- if specified then this specifies the width of the signal to the stepper
|
| 146 |
|
|
motor controller
|
| 147 |
|
|
FIFO=N_fifo
|
| 148 |
|
|
optionally specify the depth of the control word FIFO
|
| 149 |
|
|
Note: This must be a power of 2 and must be at least 16.
|
| 150 |
|
|
Note: The default is 16.\n
|
| 151 |
|
|
Theory of Operation:
|
| 152 |
|
|
Define the following:
|
| 153 |
|
|
n is the number of internal clock cycles since the control word
|
| 154 |
|
|
started being performed (i.e., these are counted after the dlock
|
| 155 |
|
|
rate is reduced by ratemethod)
|
| 156 |
|
|
F is the internal clock cycle frequency (i.e., RATE_HZ in the
|
| 157 |
|
|
second form for specifying the ratemethod
|
| 158 |
|
|
R_0 is the initial rate command
|
| 159 |
|
|
R_n is the accumulated rate after n internal clock cycles
|
| 160 |
|
|
A is the commanded acceleration
|
| 161 |
|
|
S_n is the accumulated step after n internal clock cycles
|
| 162 |
|
|
Then
|
| 163 |
|
|
R_n = R_0 + A * n
|
| 164 |
|
|
S_n = R_0 * n + A * n * (n-1) / 2
|
| 165 |
|
|
The rate R_n can be thought of as a signed fraction with the format "s0.r"
|
| 166 |
|
|
where 's' represents the sign bit, there are no bits to the left of the
|
| 167 |
|
|
decimal, and there are 'r' bits to the right of the decimal. Then the rate
|
| 168 |
|
|
can be as high as F and as low as F/2^r. Practically, the maximum rate
|
| 169 |
|
|
cannot exceed half the internal clock frequency, otherwise the "step"
|
| 170 |
|
|
signals will merge together and the stepper driver will not see distinct
|
| 171 |
|
|
driver pulses.\n
|
| 172 |
|
|
Similarly, the acceleration command A can be thought of as a signed fraction
|
| 173 |
|
|
with the format "sa.b". Here 's' again represents the sign bit and 'b'
|
| 174 |
|
|
represents the number of bits to the right of the decimial, but 'a' is a
|
| 175 |
|
|
negative number representing the first bit in A. I.e., aside from the sign
|
| 176 |
|
|
bit, A is b+a+1 bits wide. For example, the specification s-4.8 means that
|
| 177 |
|
|
A has a sign bit with 8-4+1 = 5 bits for the value of A with the leasts
|
| 178 |
|
|
significant bit representing a rate of F^2/2^8.\n
|
| 179 |
|
|
The bit widths are determined as follows: Let mR be the minimum non-zero
|
| 180 |
|
|
magnitude of the rate, mA be the minimum non-zero mangitude of the
|
| 181 |
|
|
acceleration, and MA be the mamximum magnitude of the acceleration, all in
|
| 182 |
|
|
step/sec or step/sec^2. Then\n
|
| 183 |
|
|
r = ceil(-log_2(mR/F))\n
|
| 184 |
|
|
a = floor(log_2(MA/F^2))\n
|
| 185 |
|
|
b = ceil(-log_2(mA/f^2))\n
|
| 186 |
|
|
Note: r and b may be increased by a few bits if accurate representations of
|
| 187 |
|
|
the minimum rates are needed.\n
|
| 188 |
|
|
Example:
|
| 189 |
|
|
A micro controller with an 8 MHz clock is used to operate a DRV8825 driving
|
| 190 |
|
|
a stepper motor assembly. The stepper motor has 200 steps per revolution,
|
| 191 |
|
|
can be operated in full-step or a 16-step micro step mode, has a maximum
|
| 192 |
|
|
rotation rate of 10 Hz, and has a maximum acceleration of 4 Hz/sec (i.e.,
|
| 193 |
|
|
800 full-steps/sec^2). The motor is attached to a 400mm theaded rod with a
|
| 194 |
|
|
pitch of 4mm per revolution.\n
|
| 195 |
|
|
The 1.9usec minimum high and low widths of the DRV8825 and the 8 MHz
|
| 196 |
|
|
processor clock mean that the stepper motor controller can realistically be
|
| 197 |
|
|
run at 500kHz. The rate method to divide the micro controller clock to the
|
| 198 |
|
|
internal processing rate is specified by "ratemethod" in the PERIPHERAL
|
| 199 |
|
|
command.\n
|
| 200 |
|
|
The bit widths are determine by choosing:\n
|
| 201 |
|
|
MR = 10 rev/sec * 200 full-step/rev * 16 micro-step/full-step
|
| 202 |
|
|
= 32000 micro-step/sec
|
| 203 |
|
|
==> R = -ceil(log_2(MR/F))
|
| 204 |
|
|
= -ceil(log_2((32000 micro-step/sec)/500kHz))
|
| 205 |
|
|
= 3
|
| 206 |
|
|
mR = 10 step/sec
|
| 207 |
|
|
==> r = -floor(log_2(mR/F))
|
| 208 |
|
|
= -floor(log_2((10 step/sec)/500kHz)
|
| 209 |
|
|
= 16\n
|
| 210 |
|
|
MA = 10 rev/sec^2 = 10*16*200 step/sec^2
|
| 211 |
|
|
==> A = -ceil(log_2(MA/F^2))
|
| 212 |
|
|
= -ceil(log_2((32,000 step/sec^2)/500kHz^2))
|
| 213 |
|
|
= 22\n
|
| 214 |
|
|
mA = 20 step/sec^2 (in full step mode)
|
| 215 |
|
|
==> a = -floor(log_2(mA/F^2))
|
| 216 |
|
|
= 34\n
|
| 217 |
|
|
The values R=3 and r=16 along with the sign bit mean the rate would be
|
| 218 |
|
|
stored in a signed 14-bit value. The rate requires two 8-bit writes to the
|
| 219 |
|
|
control word.\n
|
| 220 |
|
|
The values A=22 and a=34 mean the acceleration would be stored in a signed
|
| 221 |
|
|
1+(34-22) = 13 bit value. The acceleration requires two 8-bit writes to the
|
| 222 |
|
|
control word.\n
|
| 223 |
|
|
The accumulator width is set to the same value as the acceleration
|
| 224 |
|
|
resolution. This avoid non-linear trunction errors and makes the motion
|
| 225 |
|
|
profile more predictable using simple integer arithmetic.\n
|
| 226 |
|
|
The number of full steps to move from one of the of rod to the other is
|
| 227 |
|
|
(400mm/(4mm/rev)*(200steps/rev)=20_000 steps. In the micro-stepmode there
|
| 228 |
|
|
are 16 micro steps per full step, so at most 320_000 micro steps can be
|
| 229 |
|
|
performed before the full length of the rod is traversed. I.e., a 19-bit
|
| 230 |
|
|
counter will suffice for the worst-case unidirection motion. This 19-bit
|
| 231 |
|
|
count requires 3 8-bit writes to the control word.\n
|
| 232 |
|
|
A "modewidth" of 1 is specifies so that the controller can be operated in
|
| 233 |
|
|
either full step or a single hard-wired micro-step mode. If all 3 of the
|
| 234 |
|
|
DRV8825 mode pins were connected, then "modewidth=3" would need to
|
| 235 |
|
|
specified.\n
|
| 236 |
|
|
The peripheral is then specified as follows:\n
|
| 237 |
|
|
CONSTANT C_RATE_SCALE 3
|
| 238 |
|
|
CONSTANT C_RATE_RES 16
|
| 239 |
|
|
CONSTANT C_ACCEL_SCALE 22
|
| 240 |
|
|
CONSTANT C_ACCEL_RES 34
|
| 241 |
|
|
CONSTANT C_ACCUM_RES 34
|
| 242 |
|
|
CONSTANT C_COUNT_WIDTH 19
|
| 243 |
|
|
PERIPHERAL stepper_motor basename=stepper \\
|
| 244 |
|
|
outcontrol=O_stepper_control \\
|
| 245 |
|
|
outrecord=O_stepper_wr \\
|
| 246 |
|
|
outrun=O_stepper_go \\
|
| 247 |
|
|
indone=I_stepper_done \\
|
| 248 |
|
|
inerror=I_stepper_error \\
|
| 249 |
|
|
ratemethod=8_000_000/500_000 \\
|
| 250 |
|
|
ratescale=C_RATE_SCALE \\
|
| 251 |
|
|
rateres=C_RATE_RES \\
|
| 252 |
|
|
accelscale=C_ACCEL_SCALE \\
|
| 253 |
|
|
accelres=C_ACCEL_RES \\
|
| 254 |
|
|
accumres=C_ACCUM_RES \\
|
| 255 |
|
|
countwidth=C_COUNT_WIDTH \\
|
| 256 |
|
|
modewidth=1\n
|
| 257 |
|
|
and the TBD byte control words are pushed into the peripheral as follows:
|
| 258 |
|
|
R_0 14-bit initial rate stored in a 16-bit field (MSB first)
|
| 259 |
|
|
A 13-bit acceleration stored in a 16-bit field (MSB first)
|
| 260 |
|
|
COUNT 19-bit count stored in a 24-bit field (MSB first)
|
| 261 |
|
|
MODE 1-bit mode stored as the lsb of an 8-bit field
|
| 262 |
|
|
The control word is a total of 8 bytes wide.\n
|
| 263 |
|
|
To command the peripheral to accelerate from stop to 200 steps/sec in one
|
| 264 |
|
|
second in the forward direction using the full-step mode, the following
|
| 265 |
|
|
seqeuence of bytes would be written to the control port:
|
| 266 |
|
|
0x00 0x00 ; initial rate is zero
|
| 267 |
|
|
0x00 0x0E ; 200 step/sec^2 * 2^34 / 500kHz^2 = 14
|
| 268 |
|
|
0x00 0x00 0x63 ; send 100 step commands (command 100-1=99)
|
| 269 |
|
|
0x00 ; full-step mode
|
| 270 |
|
|
Note: It will take t=sqrt(2*100*2^34/14)/F = 0.99 sec to move the commanded
|
| 271 |
|
|
100 steps. At this time the speed will be r=t*14/2^34*F^2 = 201 step/sec.
|
| 272 |
|
|
A more accurate match to the commanded speed could be accomplished by adding
|
| 273 |
|
|
additional bits to the acceleration resolution at the cost of using more
|
| 274 |
|
|
FPGA resources. Alternatively, the acceleration could be commanded for 99
|
| 275 |
|
|
steps and any subsequent 200 step/sec motion could be lengthened by 1 step.
|
| 276 |
|
|
Another alternative would be to use a micro-step acceleration. Practically,
|
| 277 |
|
|
the computed command is within 0.5% of the desired step rate.\n
|
| 278 |
|
|
To command the peripheral to decelerate from 200 step/sec to zero in one
|
| 279 |
|
|
second, the following sequence of bytes would be written to the control
|
| 280 |
|
|
port:
|
| 281 |
|
|
0x00 0x01 0xDB ; 200 step/sec * 2^23 / 500kHz
|
| 282 |
|
|
0xFF 0x9C ; negative of the above acceleration
|
| 283 |
|
|
0x00 0x00 0x63 ; send 100 step commands (command 100-1=99)
|
| 284 |
|
|
0x00 ; full-step mode\n
|
| 285 |
|
|
The first of these two control words could be assembled and transmitted to
|
| 286 |
|
|
the peripheral as follows:\n
|
| 287 |
|
|
0x00 ; mode
|
| 288 |
|
|
.push24(${100-1}) ; send 100 step commands
|
| 289 |
|
|
.push16(14) ; 200 step/sec^2
|
| 290 |
|
|
.push16(0) ; initial rate is zero
|
| 291 |
|
|
${8-1} :loop swap .outport(O_stepper_control) .jumpc(loop,1-) drop
|
| 292 |
|
|
.outstrobe(O_stepper_wr) ; push the assembed control word into the FIFO
|
| 293 |
|
|
...
|
| 294 |
|
|
.outstrobe(O_stepper_go) ; perform the queued control words\n
|
| 295 |
|
|
Example:
|
| 296 |
|
|
Slave a second stepper motor controller peripheral to the preceding
|
| 297 |
|
|
periperal.\n
|
| 298 |
|
|
PERIPHERAL stepper_motor basename=slave \\
|
| 299 |
|
|
master=stepper \\
|
| 300 |
|
|
outrecord=O_slave_wr \\
|
| 301 |
|
|
outrun=O_slave_go \\
|
| 302 |
|
|
indone=I_slave_done \\
|
| 303 |
|
|
inerror=I_slave_error\n
|
| 304 |
|
|
This controller will use the internal clock generated by the first
|
| 305 |
|
|
controller and the scales, resolutions, and accumulator width will be the
|
| 306 |
|
|
same as that master peripheral. What will be different is the four I/O
|
| 307 |
|
|
ports used to operate and status the controller.
|
| 308 |
|
|
"""
|
| 309 |
|
|
|
| 310 |
|
|
def __init__(self,peripheralFile,config,param_list,loc):
|
| 311 |
|
|
# Use the externally provided file name for the peripheral
|
| 312 |
|
|
self.peripheralFile = peripheralFile
|
| 313 |
|
|
# Get the parameters.
|
| 314 |
|
|
allowables = (
|
| 315 |
11 |
sinclairrf |
( 'FIFO', r'\S+$', lambda v : self.IntPow2Method(config,v,lowLimit=16), ),
|
| 316 |
|
|
( 'accelres', r'\S+$', lambda v : self.IntMethod(config,v,lowLimit=1), ),
|
| 317 |
|
|
( 'accelscale', r'\S+$', lambda v : self.IntMethod(config,v,lowLimit=1), ),
|
| 318 |
|
|
( 'accumres', r'\S+$', lambda v : self.IntMethod(config,v,lowLimit=1), ),
|
| 319 |
|
|
( 'basename', r'[A-Za-z]\w*$', None, ),
|
| 320 |
|
|
( 'countwidth', r'\S+$', lambda v : self.IntMethod(config,v,lowLimit=1), ),
|
| 321 |
|
|
( 'indone', r'I_\w+$', None, ),
|
| 322 |
|
|
( 'inerror', r'I_\w+$', None, ),
|
| 323 |
|
|
( 'master', r'[A-Za-z]\w*$', None, ),
|
| 324 |
|
|
( 'modewidth', r'\S+$', lambda v : self.IntMethod(config,v,lowLimit=1), ),
|
| 325 |
|
|
( 'nodir', None, None, ),
|
| 326 |
|
|
( 'outcontrol', r'O_\w+$', None, ),
|
| 327 |
|
|
( 'outrecord', r'O_\w+$', None, ),
|
| 328 |
|
|
( 'outrun', r'O_\w+$', None, ),
|
| 329 |
|
|
( 'ratemethod', r'\S+$', lambda v : self.RateMethod(config,v), ),
|
| 330 |
|
|
( 'rateres', r'\S+$', lambda v : self.IntMethod(config,v,lowLimit=1), ),
|
| 331 |
|
|
( 'ratescale', r'\S+$', lambda v : self.IntMethod(config,v,lowLimit=1), ),
|
| 332 |
9 |
sinclairrf |
)
|
| 333 |
|
|
names = [a[0] for a in allowables]
|
| 334 |
|
|
for param_tuple in param_list:
|
| 335 |
|
|
param = param_tuple[0]
|
| 336 |
|
|
if param not in names:
|
| 337 |
|
|
raise SSBCCException('Unrecognized parameter "%s" at %s' % (param,loc,))
|
| 338 |
|
|
param_test = allowables[names.index(param)]
|
| 339 |
|
|
self.AddAttr(config,param,param_tuple[1],param_test[1],loc,param_test[2])
|
| 340 |
|
|
# Signals that can't be specified when a master is specified.
|
| 341 |
|
|
masterExclude = (
|
| 342 |
|
|
'accelres',
|
| 343 |
|
|
'accelscale',
|
| 344 |
|
|
'accumres',
|
| 345 |
|
|
'countwidth',
|
| 346 |
|
|
'modewidth',
|
| 347 |
10 |
sinclairrf |
'outcontrol',
|
| 348 |
9 |
sinclairrf |
'ratemethod',
|
| 349 |
|
|
'rateres',
|
| 350 |
|
|
'ratescale',
|
| 351 |
|
|
)
|
| 352 |
|
|
# Ensure the required parameters are provided.
|
| 353 |
|
|
reqdParms = (
|
| 354 |
|
|
'basename',
|
| 355 |
|
|
'indone',
|
| 356 |
|
|
'outrecord',
|
| 357 |
|
|
'outrun',
|
| 358 |
|
|
)
|
| 359 |
|
|
if not hasattr(self,'master'):
|
| 360 |
|
|
reqdParms += tuple([me for me in masterExclude if me not in ('accumres','modewidth',)])
|
| 361 |
|
|
for paramname in reqdParms:
|
| 362 |
|
|
if not hasattr(self,paramname):
|
| 363 |
|
|
raise SSBCCException('Required parameter "%s" is missing at %s' % (paramname,loc,))
|
| 364 |
|
|
# Ensure mutually exclusive parameters are not listed.
|
| 365 |
|
|
if hasattr(self,'master'):
|
| 366 |
|
|
for paramname in masterExclude:
|
| 367 |
|
|
if hasattr(self,paramname):
|
| 368 |
|
|
raise SSBCCException('Parameter "%s" cannot be specified alongside "master" at %s' % (paramname,loc,))
|
| 369 |
|
|
# Ensure basename is unique for this class of peripheral
|
| 370 |
|
|
for p in config.peripheral:
|
| 371 |
|
|
if (str(p.__class__) == str(self.__class__)) and (p.basename == self.basename):
|
| 372 |
|
|
raise SSBCCException('Duplicated stepper_motor basename "%s" at %s' % (self.basename,loc,))
|
| 373 |
|
|
# For slaves, copy the bit widths from the master peripheral.
|
| 374 |
|
|
if hasattr(self,'master'):
|
| 375 |
|
|
for p in config.peripheral:
|
| 376 |
|
|
if (str(p.__class__) == str(self.__class__)) and (p.basename == self.master):
|
| 377 |
|
|
break
|
| 378 |
|
|
else:
|
| 379 |
|
|
raise SSBCCException('Can\'t find preceding stepper_motor peripheral with basename=%s at %s ' % (self.master,loc,))
|
| 380 |
|
|
self.master = p
|
| 381 |
|
|
for paramname in masterExclude:
|
| 382 |
|
|
setattr(self,paramname,getattr(self.master,paramname))
|
| 383 |
|
|
# Set unspecified optional parameters.
|
| 384 |
|
|
if not hasattr(self,'accumres'):
|
| 385 |
|
|
self.accumres = self.accelres
|
| 386 |
|
|
if not hasattr(self,'modewidth'):
|
| 387 |
|
|
self.modewidth = 0
|
| 388 |
|
|
if not hasattr(self,'FIFO'):
|
| 389 |
|
|
self.FIFO = 16
|
| 390 |
|
|
# Ensure the parameters satisfy any mutual constraints.
|
| 391 |
|
|
if not (self.rateres < self.accelres):
|
| 392 |
|
|
raise SSBCCException('rateres should be smaller than accelres at %s' % loc)
|
| 393 |
|
|
if not (self.rateres <= self.accumres <= self.accelres):
|
| 394 |
|
|
raise SSBCCException('accumres must be between rateres and accelres at %s' % loc)
|
| 395 |
|
|
# Add the I/O port, internal signals, and the INPORT and OUTPORT symbols for this peripheral.
|
| 396 |
11 |
sinclairrf |
if not hasattr(self,'nodir'):
|
| 397 |
|
|
config.AddIO('o_%s_dir' % self.basename, 1, 'output', loc)
|
| 398 |
|
|
config.AddIO('o_%s_step' % self.basename, 1, 'output', loc)
|
| 399 |
9 |
sinclairrf |
if self.modewidth > 0:
|
| 400 |
11 |
sinclairrf |
config.AddIO('o_%s_mode' % self.basename, 1, 'output', loc)
|
| 401 |
|
|
if hasattr(self,'inerror'):
|
| 402 |
|
|
config.AddIO('i_%s_error' % self.basename, 1, 'input', loc)
|
| 403 |
9 |
sinclairrf |
config.AddSignal('s__%s__done' % self.basename, 1, loc)
|
| 404 |
|
|
self.ix_outcontrol = config.NOutports()
|
| 405 |
10 |
sinclairrf |
if not hasattr(self,'master'):
|
| 406 |
|
|
config.AddOutport((self.outcontrol,
|
| 407 |
|
|
False,
|
| 408 |
|
|
# empty list
|
| 409 |
|
|
),loc)
|
| 410 |
9 |
sinclairrf |
self.ix_outrecord = config.NOutports()
|
| 411 |
|
|
config.AddOutport((self.outrecord,
|
| 412 |
|
|
True,
|
| 413 |
|
|
# empty list
|
| 414 |
|
|
),loc)
|
| 415 |
|
|
self.ix_outrun = config.NOutports()
|
| 416 |
|
|
config.AddOutport((self.outrun,
|
| 417 |
|
|
True,
|
| 418 |
|
|
# empty list
|
| 419 |
|
|
),loc)
|
| 420 |
11 |
sinclairrf |
if hasattr(self,'inerror'):
|
| 421 |
|
|
config.AddInport((self.inerror,
|
| 422 |
9 |
sinclairrf |
('i_%s_error' % self.basename, 1, 'data', ),
|
| 423 |
|
|
), loc)
|
| 424 |
|
|
config.AddInport((self.indone,
|
| 425 |
|
|
('s__%s__done' % self.basename, 1, 'data', ),
|
| 426 |
|
|
), loc)
|
| 427 |
|
|
# Compute bit widths.
|
| 428 |
|
|
dw = config.Get('data_width')
|
| 429 |
|
|
self.data_width = dw
|
| 430 |
|
|
self.ratecmdwidth = 1 + self.rateres - self.ratescale
|
| 431 |
|
|
self.ratewidth = 1 + self.accelres - self.ratescale
|
| 432 |
|
|
self.accelwidth = 1 + self.accelres - self.accelscale
|
| 433 |
|
|
self.accumwidth = self.accumres + 1
|
| 434 |
|
|
self.controlwidth = self.ratecmdwidth
|
| 435 |
|
|
self.controlwidth += dw*int((self.accelwidth+dw-1)/dw)
|
| 436 |
|
|
self.controlwidth += dw*int((self.countwidth+dw-1)/dw)
|
| 437 |
|
|
self.controlwidth += dw*int((self.modewidth+dw-1)/dw)
|
| 438 |
|
|
self.controlwidthpacked = self.ratecmdwidth + self.accelwidth + self.countwidth + self.modewidth
|
| 439 |
|
|
# Add the 'clog2' function to the processor (if required).
|
| 440 |
|
|
config.functions['clog2'] = True
|
| 441 |
|
|
|
| 442 |
|
|
def GenVerilog(self,fp,config):
|
| 443 |
|
|
body = self.LoadCore(self.peripheralFile,'.v')
|
| 444 |
|
|
if hasattr(self,'master'):
|
| 445 |
|
|
body = re.sub(r'@MASTER_BEGIN@.*?@MASTER_END@\n','',body,flags=re.DOTALL)
|
| 446 |
|
|
else:
|
| 447 |
|
|
body = re.sub(r'@MASTER_BEGIN@\n','',body)
|
| 448 |
|
|
body = re.sub(r'@MASTER_END@\n','',body)
|
| 449 |
|
|
if self.modewidth == 0:
|
| 450 |
|
|
body = re.sub(r'@OUTMODE_BEGIN@.*?@OUTMODE_END@\n','',body,flags=re.DOTALL)
|
| 451 |
|
|
else:
|
| 452 |
|
|
body = re.sub(r'@OUTMODE_BEGIN@\n','',body)
|
| 453 |
|
|
body = re.sub(r'@OUTMODE_END@\n','',body)
|
| 454 |
11 |
sinclairrf |
if hasattr(self,'nodir'):
|
| 455 |
|
|
body = re.sub(r' *o__dir.*?\n','',body)
|
| 456 |
10 |
sinclairrf |
masterBasename = self.basename if not hasattr(self,'master') else self.master.basename
|
| 457 |
9 |
sinclairrf |
for subpair in (
|
| 458 |
|
|
( r'@ACCEL_WIDTH@', str(self.accelwidth), ),
|
| 459 |
|
|
( r'@ACCEL_RES@', str(self.accelres), ),
|
| 460 |
|
|
( r'@ACCEL_SCALE@', str(self.accelscale), ),
|
| 461 |
|
|
( r'@ACCUM_RES@', str(self.accumres), ),
|
| 462 |
|
|
( r'@ACCUM_WIDTH@', str(self.accumwidth), ),
|
| 463 |
|
|
( r'@CONTROL_WIDTH@', str(self.controlwidth), ),
|
| 464 |
|
|
( r'@CONTROL_WIDTH_PACKED@', str(self.controlwidthpacked), ),
|
| 465 |
|
|
( r'@COUNT_WIDTH@', str(self.countwidth), ),
|
| 466 |
|
|
( r'@DW@', str(self.data_width), ),
|
| 467 |
|
|
( r'@DWM1@', str(self.data_width-1), ),
|
| 468 |
|
|
( r'@FIFO_DEPTH@', str(self.FIFO), ),
|
| 469 |
|
|
( r'@IX_OUTCONTROL@', str(self.ix_outcontrol), ),
|
| 470 |
|
|
( r'@IX_OUTRECORD@', str(self.ix_outrecord), ),
|
| 471 |
|
|
( r'@IX_OUTRUN@', str(self.ix_outrun), ),
|
| 472 |
|
|
( r'@MODE_WIDTH@', str(self.modewidth), ),
|
| 473 |
|
|
( r'@NAME@', self.basename, ),
|
| 474 |
|
|
( r'@NBITS_FIFO_DEPTH@', str(CeilLog2(self.FIFO)), ),
|
| 475 |
|
|
( r'@OUTMODEWIDTH@', str(self.modewidth), ),
|
| 476 |
|
|
( r'@RATECMD_WIDTH@', str(self.ratecmdwidth), ),
|
| 477 |
|
|
( r'@RATEMETHOD@', str(self.ratemethod), ),
|
| 478 |
|
|
( r'@RATE_RES@', str(self.rateres), ),
|
| 479 |
|
|
( r'@RATE_SCALE@', str(self.ratescale), ),
|
| 480 |
|
|
( r'@RATE_WIDTH@', str(self.ratewidth), ),
|
| 481 |
|
|
( r'\bL__', 'L__%s__' % self.basename, ),
|
| 482 |
|
|
( r'\bi__', 'i_%s_' % self.basename, ),
|
| 483 |
|
|
( r'\bo__', 'o_%s_' % self.basename, ),
|
| 484 |
|
|
( r'\bs__', 's__%s__' % self.basename, ),
|
| 485 |
10 |
sinclairrf |
( r'@S__CLK_EN@', 's__%s__clk_en' % masterBasename, ),
|
| 486 |
|
|
( r'@S__INPUT_CONTROL_WORD_PACKED@', 's__%s__input_control_word_packed' % masterBasename, ),
|
| 487 |
9 |
sinclairrf |
):
|
| 488 |
|
|
body = re.sub(subpair[0],subpair[1],body)
|
| 489 |
|
|
body = self.GenVerilogFinal(config,body)
|
| 490 |
|
|
fp.write(body)
|