si_jctrl.v 11 KB
// si_jctrl.v v1 Frank Berndt
// si joychannel controller module;
// :set tabstop=4

module si_jctrl (
	sysclk, reset,
	write_data, write_enable, read_data,
	slave, start, sltx, slreq, busy,
	jchan_clk, jchan_in, jchan_ena, jchan_oe
);
	// module io ports;

	input sysclk;				// system clock;
	input reset;				// controller and system reset;

	input [63:0] write_data;	// dma write data;
	input write_enable;			// dma write enable;
	output [55:0] read_data;	// dma read data;
	input slave;				// operate as slave;
	input start;				// start master command or slave receive;
	input sltx;					// start slave transmit;
	output slreq;				// slave request status;
	output busy;				// controller interface busy;

	input jchan_clk;			// joychannel clock;
	input jchan_in;				// joychannel input;
	output jchan_ena;			// enable joychannel input reg;
	output jchan_oe;			// enable joychannel driver;

	// channel data registers;
	// dma write data are not modified and can be reused;
	// status is cleared on start of read dma;

	reg ctrlrst;			// controller reset;
	reg norsp;				// no response;
	wire reqerr;			// request error;
	reg framerr;			// frame error;
	reg collision;			// collision;
	wire [4:0] status;		// joychannel cmd status;
	reg [5:0] tx_size;		// # of bytes to transmit, including cmd;
	reg [5:0] rx_size;		// # of receive bytes;
	reg [7:0] cmd;			// controller command;
	reg [31:0] data;		// tx/rx data buffer;
	reg size_err;			// tx/rx size error;
	wire slv_update;		// overlay slave rx cmd and size;
	reg [5:0] slv_size;		// slave rx size;
	wire [7:0] slv_cmd;		// slave command;
	wire [31:0] rx_data;	// receive data;

	// tx size, rx size and cmd are only set on write dma;

	wire bad_size;			// bad tx or rx size;
	wire tx_gz;				// tx_size > 0, start fsm;
	wire rx_gz;				// rx size > 0, start fsm;

	assign bad_size = |write_data[55:54]	// tx 64..255;
		| (|write_data[47:46]);				// rx 64..255;

	always @(posedge sysclk)
	begin
		if(write_enable) begin
			tx_size <= write_data[53:48];
			rx_size <= write_data[45:40];
			size_err <= bad_size;
			cmd <= write_data[39:32];
			data[31:0] <= write_data[31:0];
		end else if(slave & slv_update) begin
			rx_size <= slv_size;
			cmd <= slv_cmd;
		end
	end

	assign rx_gz = |rx_size;
	assign tx_gz = |tx_size;

	// return channel bytes as dma read dma;

	assign reqerr = size_err;
	assign status = { norsp, reqerr, framerr, collision, ctrlrst };
	assign read_data = { 2'd0, tx_size, status, rx_size[2:0], cmd, rx_data };

	// start jchan controller;
	// start signal clears all error status bits;
	// done on first error or end of receive;
	// controller reset aborts any activity;

	wire xstart;		// any start;
	reg busy;			// joychannel controller is busy;
	wire errors;		// sum of protocol errors;
	wire done;			// sum of all completion signals;
	wire tx_done;		// transmitter done;
	wire rx_done;		// receiver done;
	wire rx_rst;		// rx bus reset;
	wire busy_rst;		// aborted when busy;

	assign xstart = start | sltx;
	assign errors = |status;
	assign done = errors | rx_done | ~tx_gz | (slave & (tx_done | ~rx_gz));
	assign busy_rst = reset & busy;

	always @(posedge sysclk)
	begin
		busy <= ~reset & ((~done & busy) | xstart);
		ctrlrst <= ~xstart & (ctrlrst | busy_rst | rx_rst);
	end

	// set slave request when rx done;
	// clear upon start of response by sltx;

	reg slreq;			// slave request pending;

	always @(posedge sysclk)
	begin
		slreq <= slave & ~sltx & (slreq | rx_done);
	end

	// tx/rx state machine;
	// shared by tx and rx because of sequential operation;
	// tx: [8:6] byte, [5:3] bit, [2:0] phase;
	// rx: [5:3] byte, [2:0] bit;

	reg [11:0] state;	// byte/bit/phase state;
	reg tx_start;		// start tx fsm;
	reg rx_start;		// start rx fsm;
	wire tx_next;		// next tx state;
	wire rx_next;		// next rx state;

	always @(posedge sysclk)
	begin
		tx_start <= tx_gz & ~reqerr & (slave? sltx : start);
		if(tx_start | rx_start)
			state <= 12'd0;
		else if(tx_next | rx_next)
			state <= state + 1;
	end

	// receiver;

	// sample jchan input;
	// use io register with clock-enable;

	assign jchan_ena = jchan_clk;

	reg [1:0] clk_del;		// delayed sampling pulse;
	reg [1:0] rx_in;		// rx sampling register;
	wire rx_edge;			// detected edge;
	wire rx_posedge;		// detected posedge;
	wire rx_negedge;		// detected negedge;

	always @(posedge sysclk)
	begin
		clk_del[0] <= jchan_clk;
		clk_del[1] <= clk_del[0];
		if(|clk_del)
			rx_in <= { rx_in[0], jchan_in };
	end

	assign rx_edge = (rx_in[1] != rx_in[0]);
	assign rx_posedge = (rx_in == 2'b01);
	assign rx_negedge = (rx_in == 2'b10);

	// pulse width disciminator;
	// counter stops at max count of 1984;
	// counter is also used by transmitter;

	reg [10:0] pw_cnt;		// pulse width counter;
	wire pw_clr;			// zero pw counter;
	wire pw_max;			// pw counter at max;

	assign pw_clr = reset | rx_edge;
	assign pw_max = &pw_cnt[10:6];

	// discrete count decodes are used to avoid comparators;
	// break signal is deprecated and not supported;
	// pulse counts:
	//	1..   tC, tE (always);
	//	1..3  small width, t1L, t0H;
	//	4..   large width, t1H, t0L;
	//	126.. tS;
	//	200.. tRST;

	wire pw_large, pw_sep, pw_rst;
	reg [2:0] pw_state;

	assign pw_large = (pw_cnt == 11'd3);	// large pulse width;
	assign pw_sep = (pw_cnt == 11'd126);	// tS;
	assign pw_rst = (pw_cnt == 11'd200);	// tRST;

	always @(posedge sysclk)
	begin
		if(pw_clr) begin
			pw_cnt <= {11{1'b0}};
			pw_state <= 3'b000;
		end else if(clk_del[1] & ~pw_max) begin
			pw_cnt <= pw_cnt + 1;
			pw_state <= pw_state | { pw_rst, pw_sep, pw_large };
		end
	end

	// determine bit value;
	// store value at rising edge of signal;
	// determine bit at falling edge of signal;

	wire rx_bit;		// large pulse width;
	reg rx_val;			// rising edge value;

	assign rx_bit = pw_state[0];	// 1 bit;

	always @(posedge sysclk)
	begin
		if(rx_posedge)
			rx_val <= rx_bit;
	end

	// receive state machine;
	// started after successful command transmission;
	// receive the specified number of bytes;
	// must wait for first falling edge of rcv bit;
	// align bytes by shifting left after rx_size received;

	wire rx_latch;		// latch rcv data bit;
	wire rx_stop;		// stop receiver on reset, errors or last bit;
	reg rx_wait;		// wait for falling edge of first rcv bit;
	reg rx_act;			// receiver active;
	wire rx_last;		// last byte received;
	reg rx_align;		// shifts until bytes aligned;
	wire rx_aligned;	// rx bytes are aligned;
	wire rx_shift;		// shift receive bits;
	wire rx_ge45;		// state count >= 4 (master), 5 (slave);
	wire rx_shift_ena;	// enable rx shift of first bytes;

	assign rx_latch = rx_act & rx_negedge;
	assign rx_stop = reset | rx_last | (rx_wait & errors);

	always @(posedge sysclk)
	begin
		rx_start <= slave? (start & rx_gz) : tx_done;
		rx_wait <= ~rx_stop & (rx_wait | rx_start);
		rx_act <= ~rx_stop & (rx_act | (rx_wait & rx_negedge));
		rx_align <= ~reset & rx_align? ~rx_aligned : rx_last;
	end

	assign rx_last = rx_act & (state[8:3] == rx_size);
	assign rx_ge45 = (state[5:3] >= { 2'b10, slave });
	assign rx_aligned = |state[8:6] | rx_ge45;
	assign rx_next = rx_latch | (rx_align & ~rx_aligned);
	assign rx_done = rx_align & rx_aligned;
	assign rx_shift = rx_next;
	assign rx_shift_ena = ~rx_aligned;

	// frame error is strictly bit timing violation;
	// can only be determined at falling edge of signal;
	// clear at start of new read dma;
	//
	// response timeout is independent of bit toggling;
	// whenever tS is seen during active receive, which
	// includes a possible channel reset;
	// when more data is expected than is sent in slave mode;
	// clear at start of new read dma;

	wire rx_framerr;	// frame error;
	wire rx_norsp;		// response timeout;

	assign rx_framerr = (rx_val == rx_bit);
	assign rx_norsp = pw_state[1] & (slave? rx_act : rx_wait);
	assign rx_rst = rx_wait & |pw_state[2:1] & ~rx_in[1];

	always @(posedge sysclk)
	begin
		if(start)
			framerr <= 0;
		else if(rx_latch)
			framerr <= framerr | rx_framerr;
		norsp <= ~start & (norsp | rx_norsp);
	end

	// tx and rx use single loadable shift register;
	// cannot use cmd & data because they cannot be modified;
	// register is 5 bytes wide for transmit;
	// receiver uses only 4 bytes of it;
	// bit order on jchan is msb-first;
	// return shift register bits as response data;
	// shift left at end of receive to align bytes;

	reg [39:0] shift_reg;	// tx/rx shift register;
	reg load_shift;			// load shift register;
	wire tx_shift;			// register tx shift enable;
	wire tx_bit;			// bit to transmit;

	always @(posedge sysclk)
	begin
		load_shift <= start | sltx;
		if(load_shift)
			shift_reg <= slave? { data, data[7:0] } : { cmd, data };
		else if(tx_shift)
			shift_reg <= { shift_reg[38:0], shift_reg[7] };
		else if(rx_shift) begin
			if(rx_shift_ena)
				shift_reg[39:8] <= shift_reg[38:7];
			shift_reg[7:0] <= { shift_reg[6:0], rx_bit };
		end
	end

	assign tx_bit = shift_reg[39];
	assign rx_data = shift_reg[31:0];

	// decode received slave commands into rx_size;
	// use lower 3 bits of command for all reserved commands;

	assign slv_cmd = { shift_reg[6:0], rx_bit };
	assign slv_update = rx_latch & (state[8:0] == 6'd7);

	always @(slv_cmd)
	begin
		case(slv_cmd)
			8'h00: slv_size <= 6'd1;		// JCTRL_STATUS;
			8'h01: slv_size <= 6'd1;		// JCTRL_QUERY;
			8'h02: slv_size <= 6'd3;		// JCTRL_READ;
			8'h03: slv_size <= 6'd35;		// JCTRL_WRITE;
			8'hff: slv_size <= 6'd1;		// JCTRL_RESET;
			default: slv_size <= { 3'd0, slv_cmd[2:0] };
		endcase
	end

	// transmitter;

	// transmit state machine;
	// must drive H for at least tS before transmitting;
	// must synchronize activation of transmitter with jchan_clk;
	// stop transmitter on reset or collision;

	reg tx_pend;			// tx pending, awaiting tS;
	reg tx_act;				// transmitter active;
	wire tx_stop;			// stop transmitter;
	wire ts_exp;			// minimum tS expired;
	wire tx_coll_stop;		// stop at collision during transmit;

	assign tx_coll_stop = tx_act & collision;
	assign tx_stop = reset | tx_done | tx_coll_stop;
	assign ts_exp = slave | (rx_in[1] & pw_state[1]);
	assign tx_next = jchan_clk & tx_act;

	always @(posedge sysclk)
	begin
		tx_pend <= ~tx_stop & (tx_pend | tx_start);
		tx_act <= ~tx_stop & (tx_act | (tx_pend & ts_exp & jchan_clk));
	end

	assign tx_shift = tx_next & (state[2:0] == 3'b111);

	assign tx_done = tx_act			// transmitter is on;
		& (state[11:6] == tx_size)	// and # of bytes sent;
		& (state[2:1] != 2'b00);	// and tE sent;

	// transmit pulse modulator;
	// register before sending to io cell;
	// delay tx_0 for collision detection;

	wire tx_0;			// sum of all 0 outputs;
	reg jchan_oe;		// drive jbus 0;
	reg tx_0del;		// delayed tx_0 bit;

	assign tx_0 = (state[2:1] == 2'b00)			// 0, 1, tE;
		| ((state[2:1] == 2'b01) & ~tx_bit)		// 1;
		| ((state[2:1] == 2'b10) & ~tx_bit);	// 1;

	always @(posedge sysclk)
	begin
		jchan_oe <= reset | (tx_act & tx_0);
		if(clk_del[0])
			tx_0del <= jchan_oe;
	end

	// collisions can only happen during transmit;
	// detect them at end of bit period by comparing
	// value on joy channel with transmit value;

	wire tx_coll;		// collision during transmit;

	assign tx_coll = tx_act & (~tx_0del != rx_in[0]);

	always @(posedge sysclk)
	begin
		if(start)
			collision <= 0;
		else if(clk_del[0])
			collision <= collision | tx_coll;
	end

endmodule