mirror of
https://github.com/olofk/serv.git
synced 2025-04-20 11:57:07 -04:00
106 lines
3.5 KiB
Verilog
106 lines
3.5 KiB
Verilog
`default_nettype none
|
|
module serv_mem_if
|
|
#(parameter WITH_CSR = 1)
|
|
(
|
|
input wire i_clk,
|
|
//State
|
|
input wire i_en,
|
|
input wire i_init,
|
|
input wire i_cnt_done,
|
|
input wire [1:0] i_bytecnt,
|
|
input wire [1:0] i_lsb,
|
|
output wire o_misalign,
|
|
output wire o_sh_done,
|
|
output wire o_sh_done_r,
|
|
//Control
|
|
input wire i_mem_op,
|
|
input wire i_shift_op,
|
|
input wire i_signed,
|
|
input wire i_word,
|
|
input wire i_half,
|
|
//Data
|
|
input wire i_op_b,
|
|
output wire o_rd,
|
|
//External interface
|
|
output wire [31:0] o_wb_dat,
|
|
output wire [3:0] o_wb_sel,
|
|
input wire [31:0] i_wb_rdt,
|
|
input wire i_wb_ack);
|
|
|
|
reg signbit;
|
|
reg [31:0] dat;
|
|
|
|
/*
|
|
Before a store operation, the data to be written needs to be shifted into
|
|
place. Depending on the address alignment, we need to shift different
|
|
amounts. One formula for calculating this is to say that we shift when
|
|
i_lsb + i_bytecnt < 4. Unfortunately, the synthesis tools don't seem to be
|
|
clever enough so the hideous expression below is used to achieve the same
|
|
thing in a more optimal way.
|
|
*/
|
|
wire byte_valid =
|
|
(!i_lsb[0] & !i_lsb[1]) |
|
|
(!i_bytecnt[0] & !i_bytecnt[1]) |
|
|
(!i_bytecnt[1] & !i_lsb[1]) |
|
|
(!i_bytecnt[1] & !i_lsb[0]) |
|
|
(!i_bytecnt[0] & !i_lsb[1]);
|
|
|
|
wire dat_en = i_shift_op | (i_en & byte_valid);
|
|
|
|
wire dat_cur =
|
|
((i_lsb == 2'd3) & dat[24]) |
|
|
((i_lsb == 2'd2) & dat[16]) |
|
|
((i_lsb == 2'd1) & dat[8]) |
|
|
((i_lsb == 2'd0) & dat[0]);
|
|
|
|
wire dat_valid =
|
|
i_word |
|
|
(i_bytecnt == 2'b00) |
|
|
(i_half & !i_bytecnt[1]);
|
|
|
|
assign o_rd = i_mem_op & (dat_valid ? dat_cur : signbit & i_signed);
|
|
|
|
assign o_wb_sel[3] = (i_lsb == 2'b11) | i_word | (i_half & i_lsb[1]);
|
|
assign o_wb_sel[2] = (i_lsb == 2'b10) | i_word;
|
|
assign o_wb_sel[1] = (i_lsb == 2'b01) | i_word | (i_half & !i_lsb[1]);
|
|
assign o_wb_sel[0] = (i_lsb == 2'b00);
|
|
|
|
assign o_wb_dat = dat;
|
|
|
|
/* The dat register has three different use cases for store, load and
|
|
shift operations.
|
|
store : Data to be written is shifted to the correct position in dat during
|
|
init by dat_en and is presented on the data bus as o_wb_dat
|
|
load : Data from the bus gets latched into dat during i_wb_ack and is then
|
|
shifted out at the appropriate time to end up in the correct
|
|
position in rd
|
|
shift : Data is shifted in during init. After that, the six LSB are used as
|
|
a downcounter (with bit 5 initially set to 0) that triggers
|
|
o_sh_done and o_sh_done_r when they wrap around to indicate that
|
|
the requested number of shifts have been performed
|
|
*/
|
|
wire [5:0] dat_shamt = (i_shift_op & !i_init) ?
|
|
//Down counter mode
|
|
dat[5:0]-1 :
|
|
//Shift reg mode with optional clearing of bit 5
|
|
{dat[6] & !(i_shift_op & i_cnt_done),dat[5:1]};
|
|
|
|
assign o_sh_done = dat_shamt[5];
|
|
assign o_sh_done_r = dat[5];
|
|
|
|
always @(posedge i_clk) begin
|
|
if (dat_en | i_wb_ack)
|
|
dat <= i_wb_ack ? i_wb_rdt : {i_op_b, dat[31:7], dat_shamt};
|
|
|
|
if (dat_valid)
|
|
signbit <= dat_cur;
|
|
end
|
|
|
|
/*
|
|
mem_misalign is checked after the init stage to decide whether to do a data
|
|
bus transaction or go to the trap state. It is only guaranteed to be correct
|
|
at this time
|
|
*/
|
|
assign o_misalign = WITH_CSR & ((i_lsb[0] & (i_word | i_half)) | (i_lsb[1] & i_word));
|
|
|
|
endmodule
|