-
Notifications
You must be signed in to change notification settings - Fork 1
/
sequencer.v
266 lines (246 loc) · 7.6 KB
/
sequencer.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
//
// Programmable sequence generator
// by Tomek Szczesny 2023
//
// This is a generator of arbitrary, synchronous bit sequences.
// This may be used for hard-coding complex or repeatable patterns.
// During operation, it reads instructions from ROM and executes them.
//
// Instructions are specifically conceived to support looping and calling
// subroutines. A stack is implemented to facilitate looping and control
// transfer.
//
// The module supports externally commanded jumps when it's in STOP state.
// This allows for programming multiple subroutines in a single ROM,
// called by external logic, and protects against interrupting unfinished
// procedures.
// FIFO can be used for scheduling tasks with no additional logic.
//
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//
// List of all valid opcodes in ROM
// Binary formats for default module parameters.
//
// x - ignored
// D - real time output data (updated on each clock cycle)
// d - additional output data
// n - parameters
//
// 000dddddddddDDDD STOP Update output register and stop.
// Sequencer may only be resumed through
// externaly commanded jump
// 001dddddddddDDDD OUT Update output register
// 011dddddddddDDDD RET Update output register and return
// (jump to address popped from stack)
// If stack is empty, act like OUT.
// 010dddddddddDDDD POP Update output register and remove a word from stack
// Usefulness of this command is challenged...
// 110nnnnnnnnnDDDD PUSHI Push immediate value "n" on stack
// 111nnnnnnnnnDDDD DECJNZ Decrement value on top of stack;
// jump to address "n" if stack top !=0
// Otherwise pop stack and continue.
// 101nnnnnnnnnDDDD JMP Performs absolute jump to address "n"
// 100nnnnnnnnnDDDD CALL Pushes incremented address on stack
// and performs absolute jump to address "n"
//
//
// Notes:
// 1. The opcode length is not fixed. Opcode width and "D" field width are module
// parameters, which will directly affect the width of "n" and "d" fields.
// These cannot be narrower than 1 bit.
// 2. Stack has the bit width of "n" field.
// 3. Stack width limits the ROM address space (to support CALL).
// 4. "data_o" output register is "d" and "D" fields combined.
// 5. All opcodes execute in one clock cycle.
//
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//
// Simple example: Stepper Motor Driver, Full Step, 5 rotations.
// This examples uses 6 words in a single 256-word ROM block.
//
// PUSHI 5 4'b0000
// loop:
// OUT 4'b1001
// OUT 4'b1100
// OUT 4'b0110
// DECJNZ loop 4'b0011
// OUT 4'b1001
// STOP 4'b0000
//
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//
// +-------------+
// clk ----->| sequencer |
// + - - - - + | +-------+ |===> data_o
// => data_o[..] => addr[2^aw] =>| | stack | | [2^(ocw-3)]
// | fifo | -> status[0] - - > jump ----->| +-------+ |===> pc[2^aw]
// | +-----+ |
// | aw | | | rom | |---> stop
// < - - - (~stop) | +-----+ |
// + - - - - + +-------------+
//
// Parameters:
// ocw - Total opcode width, ocw>=(3+1+ddw) (16)
// ddw - Width of real time data output, 0=<ddw<(ocw-3-1) (4)
// program - A bit stream describing behavior of this sequencer (STOP)
// plen - program length in opcodes (128)
// std - stack depth (256)
//
// LocalParams:
// aw - Program memory address width = clog2(plen).
// sw - Stack width = ocw-ddw-3. Must be >= aw.
//
// Ports:
// clk - Clock input, everything is posedge triggered
// addr[alen] - Input for address, for externally forced jump
// jump - When "1" and current command is "STOP", the state machine will
// jump to address "addr". It does not ovverride any other
// functions of currently processed opcode.
// data_o[...] - User data output, synchronous, updated through sequencer opcodes
// pc[2^aw] - Current Program Counter address
// stop - Positive when program executes STOP in a loop.
//
// Notes:
// 1. Physical ROM width is equal to ocw, and stack width is sw.
// This is strongly recommended to keep stack width at or below 16,
// and depth at 256. This will reduce stack to a single Block RAM cell.
// 2. Because iceRAM blocks cannot initialize their first address, PC starts
// at address "1". Thus the first ROM opcode will be ignored, but nevertheless
// must be specified by the designer.
//
`ifndef _sequencer_v_
`define _sequencer_v_
`include "rom.v"
`include "stack.v"
`define SEQ_STOP 3'b000
`define SEQ_OUT 3'b001
`define SEQ_RET 3'b011
`define SEQ_POP 3'b010
`define SEQ_PUSHI 3'b110
`define SEQ_DECJNZ 3'b111
`define SEQ_JMP 3'b101
`define SEQ_CALL 3'b100
module sequencer(
input wire clk,
input wire [aw-1:0] addr,
input wire jump,
output reg [ocw-4:0] data_o,
output wire [aw-1:0] pc,
output wire stop
);
parameter ocw = 16; // opcode width
parameter ddw = 4; // real-time data output width
parameter plen = 128; // program length
parameter [0:ocw*plen-1] program = 0;
parameter std = 256; // stack depth
localparam aw = $clog2(plen); // address width
localparam sw = ocw-ddw-3; // stack width
// Program Counter
reg [aw-1:0] pcr;
initial pcr = 1;
// Next PC position
reg [aw-1:0] pcrn;
// Misc
wire [aw-1:0] pcrp1;
assign pcrp1 = pcr + 1;
assign pc = pcr;
// Next data_o
reg [ocw-4:0] data_on;
// Current opcode
wire [ocw-1:0] oc;
wire [2:0] oc_cmd;
wire [sw-1:0] oc_param;
wire [ddw-1:0] oc_dd;
assign oc_cmd = oc[ocw-1:ocw-3];
assign oc_param = oc[ocw-4:ddw];
assign oc_dd = oc[ddw-1:0];
// Output signal "stop"
assign stop = (oc_cmd == `SEQ_STOP);
// Stack input
reg [sw-1:0] st_i;
// Stack output
wire [sw-1:0] st_o;
// Stack command
reg [2:0] st_c;
// Stack status
wire [3:0] st_s;
// ROM and Stack instantiation
rom #(
.m(plen),
.n(ocw),
.data({program}),
.content_size(plen)
) seq_rom (
.clk(clk),
.address(pcrn),
.data_o(oc)
);
stack #(
.m(std),
.n(ocw-ddw-3)
) seq_stack (
.clk(clk),
.data(st_i),
.data_o(st_o),
.command(st_c),
.status(st_s)
);
// Preparing the next data output
always@(oc_cmd[2], oc_dd, oc_param, data_o[ocw-4:ddw])
begin
// All commands update "D" output
data_on[ddw-1:0] = oc_dd;
// Commands "0xx" update "d" output
if (~oc_cmd[2]) begin
data_on[ocw-4:ddw] = oc_param;
end else begin
data_on[ocw-4:ddw] = data_o[ocw-4:ddw];
end
end
// Preparing stack input data
always@(oc_cmd[1], oc_param, pcrp1)
begin
// There are only two cases when stack is written to:
// PUSHI and CALL. In other cases stack input is ignored.
// PUSHI: 110; CALL: 100
st_i = oc_cmd[1] ? oc_param : pcrp1;
end
// Determining stack command
always @ (oc_cmd, st_o)
begin
case (oc_cmd)
`SEQ_STOP: st_c = `STK_NOP;
`SEQ_OUT: st_c = `STK_NOP;
`SEQ_RET: st_c = `STK_POP; // Shouldn't matter if empty
`SEQ_POP: st_c = `STK_POP;
`SEQ_PUSHI: st_c = `STK_PUSH;
`SEQ_DECJNZ: st_c = (st_o<2) ? `STK_POP : `STK_DEC;
`SEQ_JMP: st_c = `STK_NOP;
`SEQ_CALL: st_c = `STK_PUSH;
endcase
end
// Figuring out the next PC value
always @ (addr, jump, stop, pcr, pcrp1, st_o, st_s, oc_cmd, oc_param)
begin
if (jump && stop) pcrn = addr;
else begin
case (oc_cmd)
`SEQ_STOP: pcrn = pcr;
`SEQ_OUT: pcrn = pcrp1;
`SEQ_RET: pcrn = st_s[0] ? st_o : pcrp1;
`SEQ_POP: pcrn = pcrp1;
`SEQ_PUSHI: pcrn = pcrp1;
`SEQ_DECJNZ: pcrn = (st_o<2) ? pcrp1 : oc_param;
`SEQ_JMP: pcrn = oc_param;
`SEQ_CALL: pcrn = oc_param;
endcase
end
end
// Synchronous stuff
always@(posedge clk)
begin
data_o <= data_on;
pcr <= pcrn;
end
endmodule
`endif