feat: publish FreeRTOS C FC06 card
This commit is contained in:
+269
@@ -0,0 +1,269 @@
|
||||
/*****************************************************************************\
|
||||
| Copyright (C) 2021-2022 Luke Wren |
|
||||
| SPDX-License-Identifier: Apache-2.0 |
|
||||
\*****************************************************************************/
|
||||
|
||||
`default_nettype none
|
||||
|
||||
module hazard3_alu #(
|
||||
`include "hazard3_config.vh"
|
||||
,
|
||||
`include "hazard3_width_const.vh"
|
||||
) (
|
||||
input wire [W_ALUOP-1:0] aluop,
|
||||
input wire [6:0] funct7_32b,
|
||||
input wire [2:0] funct3_32b,
|
||||
input wire [W_DATA-1:0] op_a,
|
||||
input wire [W_DATA-1:0] op_b,
|
||||
output reg [W_DATA-1:0] result,
|
||||
output wire cmp
|
||||
);
|
||||
|
||||
`include "hazard3_ops.vh"
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Fiddle around with add/sub, comparisons etc (all related).
|
||||
|
||||
wire sub = !(aluop == ALUOP_ADD || (|EXTENSION_ZBA && aluop == ALUOP_SHXADD));
|
||||
|
||||
wire inv_op_b = sub && !(
|
||||
aluop == ALUOP_AND || aluop == ALUOP_OR || aluop == ALUOP_XOR || aluop == ALUOP_RS2
|
||||
);
|
||||
|
||||
wire [W_DATA-1:0] op_a_shifted =
|
||||
|EXTENSION_ZBA && aluop == ALUOP_SHXADD ? (
|
||||
!funct3_32b[2] ? op_a << 1 :
|
||||
!funct3_32b[1] ? op_a << 2 : op_a << 3
|
||||
) : op_a;
|
||||
|
||||
wire [W_DATA-1:0] op_b_inv = op_b ^ {W_DATA{inv_op_b}};
|
||||
|
||||
wire [W_DATA-1:0] sum = op_a_shifted + op_b_inv + {{W_DATA-1{1'b0}}, sub};
|
||||
wire [W_DATA-1:0] op_xor = op_a ^ op_b;
|
||||
|
||||
wire cmp_is_unsigned = aluop == ALUOP_LTU ||
|
||||
|EXTENSION_ZBB && aluop == ALUOP_MAXU ||
|
||||
|EXTENSION_ZBB && aluop == ALUOP_MINU;
|
||||
|
||||
wire lt = op_a[W_DATA-1] == op_b[W_DATA-1] ? sum[W_DATA-1] :
|
||||
cmp_is_unsigned ? op_b[W_DATA-1] : op_a[W_DATA-1] ;
|
||||
|
||||
assign cmp = aluop == ALUOP_SUB ? |op_xor : lt;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Separate units for shift, ctz etc
|
||||
|
||||
wire [W_DATA-1:0] shift_dout;
|
||||
wire shift_right_nleft =
|
||||
aluop == ALUOP_SRL ||
|
||||
aluop == ALUOP_SRA ||
|
||||
(|EXTENSION_ZBB && aluop == ALUOP_ROR ) ||
|
||||
(|EXTENSION_ZBS && aluop == ALUOP_BEXT ) ||
|
||||
(|EXTENSION_XH3BEXTM && aluop == ALUOP_BEXTM);
|
||||
|
||||
wire shift_arith = aluop == ALUOP_SRA;
|
||||
wire shift_rotate = |EXTENSION_ZBB & (aluop == ALUOP_ROR || aluop == ALUOP_ROL);
|
||||
|
||||
hazard3_shift_barrel #(
|
||||
`include "hazard3_config_inst.vh"
|
||||
) shifter (
|
||||
.din (op_a),
|
||||
.shamt (op_b[4:0]),
|
||||
.right_nleft (shift_right_nleft),
|
||||
.rotate (shift_rotate),
|
||||
.arith (shift_arith),
|
||||
.dout (shift_dout)
|
||||
);
|
||||
|
||||
reg [W_DATA-1:0] op_a_rev;
|
||||
always @ (*) begin: rev_op_a
|
||||
integer i;
|
||||
for (i = 0; i < W_DATA; i = i + 1) begin
|
||||
op_a_rev[i] = op_a[W_DATA - 1 - i];
|
||||
end
|
||||
end
|
||||
|
||||
// "leading" means starting at MSB. This is an LSB-first priority encoder, so
|
||||
// "leading" is reversed and "trailing" is not.
|
||||
wire [W_DATA-1:0] ctz_search_mask = aluop == ALUOP_CLZ ? op_a_rev : op_a;
|
||||
wire [W_SHAMT:0] ctz_clz;
|
||||
|
||||
hazard3_priority_encode #(
|
||||
.W_REQ (W_DATA),
|
||||
.HIGHEST_WINS (0)
|
||||
) ctz_priority_encode (
|
||||
.req (ctz_search_mask),
|
||||
.gnt (ctz_clz[W_SHAMT-1:0])
|
||||
);
|
||||
// Special case: all-zeroes returns XLEN
|
||||
assign ctz_clz[W_SHAMT] = ~|op_a;
|
||||
|
||||
reg [W_SHAMT:0] cpop;
|
||||
always @ (*) begin: cpop_count
|
||||
integer i;
|
||||
cpop = {W_SHAMT+1{1'b0}};
|
||||
for (i = 0; i < W_DATA; i = i + 1) begin
|
||||
cpop = cpop + {{W_SHAMT{1'b0}}, op_a[i]};
|
||||
end
|
||||
end
|
||||
|
||||
reg [2*W_DATA-1:0] clmul64;
|
||||
|
||||
always @ (*) begin: clmul_mul
|
||||
integer i;
|
||||
clmul64 = {2*W_DATA{1'b0}};
|
||||
for (i = 0; i < W_DATA; i = i + 1) begin
|
||||
clmul64 = clmul64 ^ (({{W_DATA{1'b0}}, op_a} << i) & {2*W_DATA{op_b[i]}});
|
||||
end
|
||||
end
|
||||
|
||||
// funct3: 1=clmul, 2=clmulr, 3=clmulh, never 0.
|
||||
wire [W_DATA-1:0] clmul =
|
||||
!funct3_32b[1] ? clmul64[31: 0] :
|
||||
!funct3_32b[0] ? clmul64[62:31] : clmul64[63:32];
|
||||
|
||||
reg [W_DATA-1:0] zip;
|
||||
reg [W_DATA-1:0] unzip;
|
||||
always @ (*) begin: do_zip_unzip
|
||||
integer i;
|
||||
for (i = 0; i < W_DATA; i = i + 1) begin
|
||||
zip[i] = op_a[{i[0], i[4:1]}]; // Alternate high/low halves
|
||||
unzip[i] = op_a[{i[3:0], i[4]}]; // All even then all odd
|
||||
end
|
||||
end
|
||||
|
||||
reg [W_DATA-1:0] xperm8;
|
||||
always @ (*) begin: do_xperm8
|
||||
integer i;
|
||||
for (i = 0; i < W_DATA; i = i + 8) begin
|
||||
if (|op_b[i + 2 +: 6]) begin
|
||||
xperm8[i +: 8] = 8'h00;
|
||||
end else begin
|
||||
xperm8[i +: 8] = op_a[8 * op_b[i +: 2] +: 8];
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
reg [W_DATA-1:0] xperm4;
|
||||
always @ (*) begin: do_xperm4
|
||||
integer i;
|
||||
for (i = 0; i < W_DATA; i = i + 4) begin
|
||||
if (op_b[i + 3]) begin
|
||||
xperm4[i +: 4] = 4'h0;
|
||||
end else begin
|
||||
xperm4[i +: 4] = op_a[4 * op_b[i +: 3] +: 4];
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Output mux, with simple operations inline
|
||||
|
||||
// iCE40: We can implement all bitwise ops with 1 LUT4/bit total, since each
|
||||
// result bit uses only two operand bits. Much better than feeding each into
|
||||
// main mux tree. Doesn't matter for big-LUT FPGAs or for implementations with
|
||||
// bitmanip extensions enabled.
|
||||
|
||||
reg [W_DATA-1:0] bitwise;
|
||||
|
||||
always @ (*) begin: bitwise_ops
|
||||
case (aluop[1:0])
|
||||
ALUOP_AND[1:0]: bitwise = op_a & op_b_inv;
|
||||
ALUOP_OR [1:0]: bitwise = op_a | op_b_inv;
|
||||
ALUOP_XOR[1:0]: bitwise = op_a ^ op_b_inv;
|
||||
ALUOP_RS2[1:0]: bitwise = op_b_inv;
|
||||
endcase
|
||||
end
|
||||
|
||||
wire [W_DATA-1:0] zbs_mask = {{W_DATA-1{1'b0}}, 1'b1} << op_b[W_SHAMT-1:0];
|
||||
|
||||
always @ (*) begin
|
||||
casez ({|EXTENSION_A, |EXTENSION_ZBA, |EXTENSION_ZBB, |EXTENSION_ZBC,
|
||||
|EXTENSION_ZBS, |EXTENSION_ZBKB, |EXTENSION_ZBKX, |EXTENSION_XH3BEXTM, aluop})
|
||||
// Base ISA
|
||||
{8'bzzzzzzzz, ALUOP_ADD }: result = sum;
|
||||
{8'bzzzzzzzz, ALUOP_SUB }: result = sum;
|
||||
{8'bzzzzzzzz, ALUOP_LT }: result = {{W_DATA-1{1'b0}}, lt};
|
||||
{8'bzzzzzzzz, ALUOP_LTU }: result = {{W_DATA-1{1'b0}}, lt};
|
||||
{8'bzzzzzzzz, ALUOP_SRL }: result = shift_dout;
|
||||
{8'bzzzzzzzz, ALUOP_SRA }: result = shift_dout;
|
||||
{8'bzzzzzzzz, ALUOP_SLL }: result = shift_dout;
|
||||
// A or Zbb (written this way to avoid case overlap)
|
||||
{8'b1zzzzzzz, ALUOP_MAX },
|
||||
{8'b0z1zzzzz, ALUOP_MAX }: result = lt ? op_b : op_a;
|
||||
{8'b1zzzzzzz, ALUOP_MIN },
|
||||
{8'b0z1zzzzz, ALUOP_MIN }: result = lt ? op_a : op_b;
|
||||
{8'b1zzzzzzz, ALUOP_MAXU },
|
||||
{8'b0z1zzzzz, ALUOP_MAXU }: result = lt ? op_b : op_a;
|
||||
{8'b1zzzzzzz, ALUOP_MINU },
|
||||
{8'b0z1zzzzz, ALUOP_MINU }: result = lt ? op_a : op_b;
|
||||
// Zba
|
||||
{8'bz1zzzzzz, ALUOP_SHXADD }: result = sum;
|
||||
// Zbb
|
||||
{8'bzz1zzzzz, ALUOP_ANDN }: result = bitwise;
|
||||
{8'bzz1zzzzz, ALUOP_ORN }: result = bitwise;
|
||||
{8'bzz1zzzzz, ALUOP_XNOR }: result = bitwise;
|
||||
{8'bzz1zzzzz, ALUOP_CLZ }: result = {{W_DATA-W_SHAMT-1{1'b0}}, ctz_clz};
|
||||
{8'bzz1zzzzz, ALUOP_CTZ }: result = {{W_DATA-W_SHAMT-1{1'b0}}, ctz_clz};
|
||||
{8'bzz1zzzzz, ALUOP_CPOP }: result = {{W_DATA-W_SHAMT-1{1'b0}}, cpop};
|
||||
{8'bzz1zzzzz, ALUOP_SEXT_B }: result = {{W_DATA-8{op_a[7]}}, op_a[7:0]};
|
||||
{8'bzz1zzzzz, ALUOP_SEXT_H }: result = {{W_DATA-16{op_a[15]}}, op_a[15:0]};
|
||||
{8'bzz1zzzzz, ALUOP_ZEXT_H }: result = {{W_DATA-16{1'b0}}, op_a[15:0]};
|
||||
{8'bzz1zzzzz, ALUOP_ORC_B }: result = {{8{|op_a[31:24]}}, {8{|op_a[23:16]}}, {8{|op_a[15:8]}}, {8{|op_a[7:0]}}};
|
||||
{8'bzz1zzzzz, ALUOP_REV8 }: result = {op_a[7:0], op_a[15:8], op_a[23:16], op_a[31:24]};
|
||||
{8'bzz1zzzzz, ALUOP_ROL }: result = shift_dout;
|
||||
{8'bzz1zzzzz, ALUOP_ROR }: result = shift_dout;
|
||||
// Zbc
|
||||
{8'bzzz1zzzz, ALUOP_CLMUL }: result = clmul;
|
||||
// Zbs
|
||||
{8'bzzzz1zzz, ALUOP_BCLR }: result = op_a & ~zbs_mask;
|
||||
{8'bzzzz1zzz, ALUOP_BSET }: result = op_a | zbs_mask;
|
||||
{8'bzzzz1zzz, ALUOP_BINV }: result = op_a ^ zbs_mask;
|
||||
{8'bzzzz1zzz, ALUOP_BEXT }: result = {{W_DATA-1{1'b0}}, shift_dout[0]};
|
||||
// Zbkb
|
||||
{8'bzzzzz1zz, ALUOP_PACK }: result = {op_b[15:0], op_a[15:0]};
|
||||
{8'bzzzzz1zz, ALUOP_PACKH }: result = {{W_DATA-16{1'b0}}, op_b[7:0], op_a[7:0]};
|
||||
{8'bzzzzz1zz, ALUOP_BREV8 }: result = {op_a_rev[7:0], op_a_rev[15:8], op_a_rev[23:16], op_a_rev[31:24]};
|
||||
{8'bzzzzz1zz, ALUOP_UNZIP }: result = unzip;
|
||||
{8'bzzzzz1zz, ALUOP_ZIP }: result = zip;
|
||||
// Zbkx
|
||||
{8'bzzzzzz1z, ALUOP_XPERM }: result = funct3_32b[2] ? xperm8 : xperm4;
|
||||
// Xh3bextm
|
||||
{8'bzzzzzzz1, ALUOP_BEXTM }: result = shift_dout & {24'h0, {~(8'hfe << funct7_32b[3:1])}};
|
||||
|
||||
default: result = bitwise;
|
||||
endcase
|
||||
end
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Properties for base-ISA instructions
|
||||
|
||||
`ifdef HAZARD3_ASSERTIONS
|
||||
`ifndef RISCV_FORMAL
|
||||
// Really we're just interested in the shifts and comparisons, as these are
|
||||
// the nontrivial ones. However, easier to test everything!
|
||||
|
||||
wire clk;
|
||||
always @ (posedge clk) begin
|
||||
case(aluop)
|
||||
default: begin end
|
||||
ALUOP_ADD: assert(result == op_a + op_b);
|
||||
ALUOP_SUB: assert(result == op_a - op_b);
|
||||
ALUOP_LT: assert(result == $signed(op_a) < $signed(op_b));
|
||||
ALUOP_LTU: assert(result == op_a < op_b);
|
||||
ALUOP_AND: assert(result == (op_a & op_b));
|
||||
ALUOP_OR: assert(result == (op_a | op_b));
|
||||
ALUOP_XOR: assert(result == (op_a ^ op_b));
|
||||
ALUOP_SRL: assert(result == op_a >> op_b[4:0]);
|
||||
ALUOP_SRA: assert($signed(result) == $signed(op_a) >>> $signed(op_b[4:0]));
|
||||
ALUOP_SLL: assert(result == op_a << op_b[4:0]);
|
||||
endcase
|
||||
end
|
||||
`endif
|
||||
`endif
|
||||
|
||||
endmodule
|
||||
|
||||
`ifndef YOSYS
|
||||
`default_nettype wire
|
||||
`endif
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/*****************************************************************************\
|
||||
| Copyright (C) 2022 Luke Wren |
|
||||
| SPDX-License-Identifier: Apache-2.0 |
|
||||
\*****************************************************************************/
|
||||
|
||||
`default_nettype none
|
||||
|
||||
// The branch decision path through the ALU is slow because:
|
||||
//
|
||||
// - Sees immediates and PC on its inputs, as well as regs
|
||||
// - Add/sub rather than just add (with complex decode of the sub condition)
|
||||
// - 2 extra mux layers in front of adder if Zba extension is enabled
|
||||
//
|
||||
// So there is sometimes timing benefit to a dedicated branch comparator.
|
||||
|
||||
module hazard3_branchcmp #(
|
||||
`include "hazard3_config.vh"
|
||||
,
|
||||
`include "hazard3_width_const.vh"
|
||||
) (
|
||||
input wire [31:0] cir,
|
||||
input wire [W_DATA-1:0] op_a,
|
||||
input wire [W_DATA-1:0] op_b,
|
||||
output wire cmp
|
||||
);
|
||||
|
||||
`include "hazard3_ops.vh"
|
||||
|
||||
wire [W_DATA-1:0] diff = op_a - op_b;
|
||||
|
||||
// funct3 instruction
|
||||
// ------------------
|
||||
// 000 BEQ
|
||||
// 001 BNE
|
||||
// 100 BLT
|
||||
// 101 BGE
|
||||
// 110 BLTU
|
||||
// 111 BGEU
|
||||
|
||||
wire cmp_is_unsigned = cir[13];
|
||||
|
||||
wire lt = op_a[W_DATA-1] == op_b[W_DATA-1] ? diff[W_DATA-1] :
|
||||
cmp_is_unsigned ? op_b[W_DATA-1] :
|
||||
op_a[W_DATA-1] ;
|
||||
|
||||
assign cmp = cir[14] ? lt : op_a != op_b;
|
||||
|
||||
endmodule
|
||||
|
||||
`ifndef YOSYS
|
||||
`default_nettype wire
|
||||
`endif
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
/*****************************************************************************\
|
||||
| Copyright (C) 2021-2022 Luke Wren |
|
||||
| SPDX-License-Identifier: Apache-2.0 |
|
||||
\*****************************************************************************/
|
||||
|
||||
// MUL-only (cfg: MUL_FAST) and MUL/MULH/MULHU/MULHSU (cfg: MUL_FAST &&
|
||||
// MULH_FAST) are handled by different circuits. In either case it's a simple
|
||||
// behavioural multiply, and we rely on inference to get good performance on
|
||||
// FPGA.
|
||||
|
||||
`default_nettype none
|
||||
|
||||
module hazard3_mul_fast #(
|
||||
`include "hazard3_config.vh"
|
||||
,
|
||||
`include "hazard3_width_const.vh"
|
||||
) (
|
||||
input wire clk,
|
||||
input wire rst_n,
|
||||
|
||||
input wire [W_MULOP-1:0] op,
|
||||
input wire op_vld,
|
||||
input wire [W_DATA-1:0] op_a,
|
||||
input wire [W_DATA-1:0] op_b,
|
||||
|
||||
output wire [W_DATA-1:0] result,
|
||||
output reg result_vld
|
||||
);
|
||||
|
||||
`include "hazard3_ops.vh"
|
||||
|
||||
localparam XLEN = W_DATA;
|
||||
|
||||
//synthesis translate_off
|
||||
generate if (MULH_FAST && !MUL_FAST) begin: err_require_mul_fast_for_mulh
|
||||
initial $fatal("%m: MULH_FAST requires that MUL_FAST is also set.");
|
||||
end endgenerate
|
||||
generate if (MUL_FASTER && !MUL_FAST) begin: err_require_mul_fast_for_faster
|
||||
initial $fatal("%m: MUL_FASTER requires that MUL_FAST is also set.");
|
||||
end endgenerate
|
||||
//synthesis translate_on
|
||||
|
||||
// Latency of 1:
|
||||
always @ (posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin
|
||||
result_vld <= 1'b0;
|
||||
end else begin
|
||||
result_vld <= op_vld;
|
||||
end
|
||||
end
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Fast MUL only
|
||||
|
||||
generate
|
||||
if (!MULH_FAST) begin: mul_only
|
||||
|
||||
// This pipestage is folded into the front of the DSP tiles on UP5k. Note the
|
||||
// intention is to register the bypassed core regs at the end of X (since
|
||||
// bypass is quite slow), then perform multiply combinatorially in stage M,
|
||||
// and mux into MW result register.
|
||||
|
||||
reg [XLEN-1:0] op_a_r;
|
||||
reg [XLEN-1:0] op_b_r;
|
||||
|
||||
if (MUL_FASTER) begin: op_passthrough
|
||||
always @ (*) begin
|
||||
op_a_r = op_a;
|
||||
op_b_r = op_b;
|
||||
end
|
||||
end else begin: op_register
|
||||
always @ (posedge clk) begin
|
||||
if (op_vld) begin
|
||||
op_a_r <= op_a;
|
||||
op_b_r <= op_b;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// This should be inferred as 3 DSP tiles on UP5k:
|
||||
//
|
||||
// 1. Register then multiply a[15: 0] and b[15: 0]
|
||||
// 2. Register then multiply a[31:16] and b[15: 0], then directly add output of 1
|
||||
// 3. Register then multiply a[15: 0] and b[31:16], then directly add output of 2
|
||||
//
|
||||
// So there is quite a long path (1x 16-bit multiply, then 2x 16-bit add). On
|
||||
// other platforms you may just end up with a pile of gates.
|
||||
|
||||
`ifndef RISCV_FORMAL_ALTOPS
|
||||
|
||||
assign result = op_a_r * op_b_r;
|
||||
|
||||
`else
|
||||
|
||||
// riscv-formal can use a simpler function, since it's just confirming the
|
||||
// result is correctly hooked up.
|
||||
assign result = result_vld ? (op_a_r + op_b_r) ^ 32'h5876063e : 32'hdeadbeef;
|
||||
|
||||
`endif
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Fast MUL/MULH/MULHU/MULHSU
|
||||
|
||||
end else begin: mul_and_mulh
|
||||
|
||||
reg [XLEN-1:0] op_a_r;
|
||||
reg [XLEN-1:0] op_b_r;
|
||||
reg [W_MULOP-1:0] op_r;
|
||||
|
||||
if (MUL_FASTER) begin: op_passthrough
|
||||
always @ (*) begin
|
||||
op_a_r = op_a;
|
||||
op_b_r = op_b;
|
||||
op_r = op;
|
||||
end
|
||||
end else begin: op_register
|
||||
always @ (posedge clk) begin
|
||||
if (op_vld) begin
|
||||
op_a_r <= op_a;
|
||||
op_b_r <= op_b;
|
||||
op_r <= op;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
wire op_a_signed = op_r == M_OP_MULH || op_r == M_OP_MULHSU;
|
||||
wire op_b_signed = op_r == M_OP_MULH;
|
||||
|
||||
wire [2*XLEN-1:0] op_a_sext = {
|
||||
{XLEN{op_a_r[XLEN - 1] && op_a_signed}},
|
||||
op_a_r
|
||||
};
|
||||
|
||||
wire [2*XLEN-1:0] op_b_sext = {
|
||||
{XLEN{op_b_r[XLEN - 1] && op_b_signed}},
|
||||
op_b_r
|
||||
};
|
||||
|
||||
wire [2*XLEN-1:0] result_full = op_a_sext * op_b_sext;
|
||||
|
||||
`ifndef RISCV_FORMAL_ALTOPS
|
||||
|
||||
assign result = op_r == M_OP_MUL ? result_full[0 +: XLEN] : result_full[XLEN +: XLEN];
|
||||
|
||||
`else
|
||||
|
||||
assign result =
|
||||
op_r == M_OP_MULH ? (op_a_r + op_b_r) ^ 32'hf6583fb7 :
|
||||
op_r == M_OP_MULHSU ? (op_a_r - op_b_r) ^ 32'hecfbe137 :
|
||||
op_r == M_OP_MULHU ? (op_a_r + op_b_r) ^ 32'h949ce5e8 :
|
||||
op_r == M_OP_MUL ? (op_a_r + op_b_r) ^ 32'h5876063e : 32'hdeadbeef;
|
||||
|
||||
`endif
|
||||
|
||||
end
|
||||
endgenerate
|
||||
|
||||
endmodule
|
||||
|
||||
`ifndef YOSYS
|
||||
`default_nettype wire
|
||||
`endif
|
||||
+294
@@ -0,0 +1,294 @@
|
||||
/*****************************************************************************\
|
||||
| Copyright (C) 2021-2022 Luke Wren |
|
||||
| SPDX-License-Identifier: Apache-2.0 |
|
||||
\*****************************************************************************/
|
||||
|
||||
// Combined multiply/divide/modulo circuit. All operations performed at 1 bit
|
||||
// per clock; aiming for minimal resource usage on iCE40 FPGA. Optionally the
|
||||
// circuit can be unrolled for slightly higher performance.
|
||||
//
|
||||
// When op_kill is high, the current calculation halts immediately. op_vld can
|
||||
// be asserted on the same cycle, and the new calculation begins without
|
||||
// delay, regardless of op_rdy. This may be used by the processor on e.g.
|
||||
// mispredict or trap.
|
||||
//
|
||||
// The actual multiply/divide hardware is unsigned. We handle signedness at
|
||||
// input/output.
|
||||
|
||||
`default_nettype none
|
||||
|
||||
module hazard3_muldiv_seq #(
|
||||
`include "hazard3_config.vh"
|
||||
,
|
||||
`include "hazard3_width_const.vh"
|
||||
) (
|
||||
input wire clk,
|
||||
input wire rst_n,
|
||||
input wire [W_MULOP-1:0] op,
|
||||
input wire op_vld,
|
||||
output wire op_rdy,
|
||||
input wire op_kill,
|
||||
input wire [W_DATA-1:0] op_a,
|
||||
input wire [W_DATA-1:0] op_b,
|
||||
|
||||
output wire [W_DATA-1:0] result_h, // mulh* or rem*
|
||||
output wire [W_DATA-1:0] result_l, // mul or div*
|
||||
output wire result_vld
|
||||
);
|
||||
|
||||
`include "hazard3_ops.vh"
|
||||
|
||||
//synthesis translate_off
|
||||
generate if (|(MULDIV_UNROLL & (MULDIV_UNROLL - 1)) || ~|MULDIV_UNROLL) begin: err_pow2
|
||||
initial $fatal("%m: MULDIV_UNROLL must be a positive power of 2");
|
||||
end endgenerate
|
||||
//synthesis translate_on
|
||||
|
||||
localparam XLEN = W_DATA;
|
||||
parameter W_CTR = $clog2(XLEN + 1);
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Operation decode, operand sign adjustment
|
||||
|
||||
// On the first cycle, op_a and op_b go straight through to the accumulator
|
||||
// and the divisor/multiplicand register. They are then adjusted in-place
|
||||
// on the next cycle. This allows the same circuits to be reused for sign
|
||||
// adjustment before output (and helps input timing).
|
||||
|
||||
reg [W_MULOP-1:0] op_r;
|
||||
reg [2*XLEN-1:0] accum;
|
||||
reg [XLEN-1:0] op_b_r;
|
||||
reg op_a_neg_r;
|
||||
reg op_b_neg_r;
|
||||
|
||||
wire op_a_signed =
|
||||
op_r == M_OP_MULH ||
|
||||
op_r == M_OP_MULHSU ||
|
||||
op_r == M_OP_DIV ||
|
||||
op_r == M_OP_REM;
|
||||
|
||||
wire op_b_signed =
|
||||
op_r == M_OP_MULH ||
|
||||
op_r == M_OP_DIV ||
|
||||
op_r == M_OP_REM;
|
||||
|
||||
wire op_a_neg = op_a_signed && accum[XLEN-1];
|
||||
wire op_b_neg = op_b_signed && op_b_r[XLEN-1];
|
||||
|
||||
// Non-divide parts of the circuit should be constant-folded if all the MUL
|
||||
// operations are handled by the fast multiplier
|
||||
|
||||
wire is_div = op_r[2] || (MUL_FAST && MULH_FAST);
|
||||
|
||||
// Controls for modifying sign of all/part of accumulator
|
||||
wire accum_neg_l;
|
||||
wire accum_inv_h;
|
||||
wire accum_incr_h;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Arithmetic circuit
|
||||
|
||||
// Combinatorials:
|
||||
reg [2*XLEN-1:0] accum_next;
|
||||
reg [2*XLEN-1:0] addend;
|
||||
reg [2*XLEN-1:0] shift_tmp;
|
||||
reg [2*XLEN-1:0] addsub_tmp;
|
||||
reg neg_l_borrow;
|
||||
|
||||
always @ (*) begin: alu
|
||||
integer i;
|
||||
// Multiply/divide iteration layers
|
||||
accum_next = accum;
|
||||
addend = {2*XLEN{1'b0}};
|
||||
addsub_tmp = {2*XLEN{1'b0}};
|
||||
neg_l_borrow = 1'b0;
|
||||
for (i = 0; i < MULDIV_UNROLL; i = i + 1) begin
|
||||
addend = {is_div && |op_b_r, op_b_r, {XLEN-1{1'b0}}};
|
||||
shift_tmp = is_div ? accum_next : accum_next >> 1;
|
||||
addsub_tmp = shift_tmp + addend;
|
||||
accum_next = (is_div ? !addsub_tmp[2 * XLEN - 1] : accum_next[0]) ?
|
||||
addsub_tmp : shift_tmp;
|
||||
if (is_div)
|
||||
accum_next = {accum_next[2*XLEN-2:0], !addsub_tmp[2 * XLEN - 1]};
|
||||
end
|
||||
// Alternative path for negation of all/part of accumulator
|
||||
if (accum_neg_l)
|
||||
{neg_l_borrow, accum_next[XLEN-1:0]} = {~accum[XLEN-1:0]} + 1'b1;
|
||||
if (accum_incr_h || accum_inv_h)
|
||||
accum_next[XLEN +: XLEN] = (accum[XLEN +: XLEN] ^ {XLEN{accum_inv_h}})
|
||||
+ {{XLEN-1{1'b0}}, accum_incr_h};
|
||||
end
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Main state machine
|
||||
|
||||
reg sign_preadj_done;
|
||||
reg [W_CTR-1:0] ctr;
|
||||
reg sign_postadj_done;
|
||||
reg sign_postadj_carry;
|
||||
|
||||
localparam CTR_TOP = XLEN[W_CTR-1:0];
|
||||
|
||||
always @ (posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin
|
||||
ctr <= {W_CTR{1'b0}};
|
||||
sign_preadj_done <= 1'b1;
|
||||
sign_postadj_done <= 1'b1;
|
||||
sign_postadj_carry <= 1'b0;
|
||||
op_r <= {W_MULOP{1'b0}};
|
||||
op_a_neg_r <= 1'b0;
|
||||
op_b_neg_r <= 1'b0;
|
||||
op_b_r <= {XLEN{1'b0}};
|
||||
accum <= {XLEN*2{1'b0}};
|
||||
end else if (op_kill || (op_vld && op_rdy)) begin
|
||||
// Initialise circuit with operands + state
|
||||
ctr <= op_vld ? CTR_TOP : {W_CTR{1'b0}};
|
||||
sign_preadj_done <= !op_vld;
|
||||
sign_postadj_done <= !op_vld;
|
||||
sign_postadj_carry <= 1'b0;
|
||||
op_r <= op;
|
||||
op_b_r <= op_b;
|
||||
accum <= {{XLEN{1'b0}}, op_a};
|
||||
end else if (!sign_preadj_done) begin
|
||||
// Pre-adjust sign if necessary, else perform first iteration immediately
|
||||
op_a_neg_r <= op_a_neg;
|
||||
op_b_neg_r <= op_b_neg;
|
||||
sign_preadj_done <= 1'b1;
|
||||
if (accum_neg_l || (op_b_neg ^ is_div)) begin
|
||||
if (accum_neg_l)
|
||||
accum[0 +: XLEN] <= accum_next[0 +: XLEN];
|
||||
if (op_b_neg ^ is_div)
|
||||
op_b_r <= -op_b_r;
|
||||
end else begin
|
||||
ctr <= ctr - MULDIV_UNROLL[W_CTR-1:0];
|
||||
accum <= accum_next;
|
||||
end
|
||||
end else if (|ctr) begin
|
||||
ctr <= ctr - MULDIV_UNROLL[W_CTR-1:0];
|
||||
accum <= accum_next;
|
||||
end else if (!sign_postadj_done || sign_postadj_carry) begin
|
||||
sign_postadj_done <= 1'b1;
|
||||
if (accum_inv_h || accum_incr_h)
|
||||
accum[XLEN +: XLEN] <= accum_next[XLEN +: XLEN];
|
||||
if (accum_neg_l) begin
|
||||
accum[0 +: XLEN] <= accum_next[0 +: XLEN];
|
||||
if (!is_div) begin
|
||||
sign_postadj_carry <= neg_l_borrow;
|
||||
sign_postadj_done <= !neg_l_borrow;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Sign adjustment control
|
||||
|
||||
// Pre-adjustment: for any a, b we want |a|, |b|. Note that the magnitude of any
|
||||
// 32-bit signed integer is representable by a 32-bit unsigned integer.
|
||||
|
||||
// Post-adjustment for division:
|
||||
// We seek q, r to satisfy a = b * q + r, where a and b are given,
|
||||
// and |r| < |b|. One way to do this is if
|
||||
// sgn(r) = sgn(a)
|
||||
// sgn(q) = sgn(a) ^ sgn(b)
|
||||
// This has additional nice properties like
|
||||
// -(a / b) = (-a) / b = a / (-b)
|
||||
|
||||
// Post-adjustment for multiplication:
|
||||
// We have calculated the 2*XLEN result of |a| * |b|.
|
||||
// Negate the entire accumulator if sgn(a) ^ sgn(b).
|
||||
// This is done in two steps (to share div/mod circuit, and avoid 64-bit carry):
|
||||
// - Negate lower half of accumulator, and invert upper half
|
||||
// - Increment upper half if lower half carried
|
||||
|
||||
wire do_postadj = ~|{ctr, sign_postadj_done};
|
||||
wire op_signs_differ = op_a_neg_r ^ op_b_neg_r;
|
||||
|
||||
assign accum_neg_l =
|
||||
!sign_preadj_done && op_a_neg ||
|
||||
do_postadj && !sign_postadj_carry && op_signs_differ && !(is_div && ~|op_b_r);
|
||||
|
||||
assign {accum_incr_h, accum_inv_h} =
|
||||
do_postadj && is_div && op_a_neg_r ? 2'b11 :
|
||||
do_postadj && !is_div && op_signs_differ && !sign_postadj_carry ? 2'b01 :
|
||||
do_postadj && !is_div && op_signs_differ && sign_postadj_carry ? 2'b10 :
|
||||
2'b00 ;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Outputs
|
||||
|
||||
assign op_rdy = ~|{ctr, accum_neg_l, accum_incr_h, accum_inv_h};
|
||||
assign result_vld = op_rdy;
|
||||
|
||||
`ifndef RISCV_FORMAL_ALTOPS
|
||||
|
||||
assign {result_h, result_l} = accum;
|
||||
|
||||
`else
|
||||
|
||||
// Provide arithmetically simpler alternative operations, to speed up formal checks
|
||||
always assert(XLEN == 32);
|
||||
|
||||
reg [XLEN-1:0] fml_a_saved;
|
||||
reg [XLEN-1:0] fml_b_saved;
|
||||
|
||||
always @ (posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin
|
||||
fml_a_saved <= {XLEN{1'b0}};
|
||||
fml_b_saved <= {XLEN{1'b0}};
|
||||
end else if (op_vld && op_rdy) begin
|
||||
fml_a_saved <= op_a;
|
||||
fml_b_saved <= op_b;
|
||||
end
|
||||
end
|
||||
|
||||
assign result_h =
|
||||
op_r == M_OP_MULH ? (fml_a_saved + fml_b_saved) ^ 32'hf6583fb7 :
|
||||
op_r == M_OP_MULHSU ? (fml_a_saved - fml_b_saved) ^ 32'hecfbe137 :
|
||||
op_r == M_OP_MULHU ? (fml_a_saved + fml_b_saved) ^ 32'h949ce5e8 :
|
||||
op_r == M_OP_REM ? (fml_a_saved - fml_b_saved) ^ 32'h8da68fa5 :
|
||||
op_r == M_OP_REMU ? (fml_a_saved - fml_b_saved) ^ 32'h3138d0e1 : 32'hdeadbeef;
|
||||
|
||||
assign result_l =
|
||||
op_r == M_OP_MUL ? (fml_a_saved + fml_b_saved) ^ 32'h5876063e :
|
||||
op_r == M_OP_DIV ? (fml_a_saved - fml_b_saved) ^ 32'h7f8529ec :
|
||||
op_r == M_OP_DIVU ? (fml_a_saved - fml_b_saved) ^ 32'h10e8fd70 : 32'hdeadbeef;
|
||||
|
||||
`endif
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Interface properties
|
||||
|
||||
`ifdef HAZARD3_ASSERTIONS
|
||||
|
||||
always @ (posedge clk) if (rst_n && $past(rst_n)) begin: properties
|
||||
integer i;
|
||||
reg alive;
|
||||
|
||||
if ($past(op_rdy && !op_vld))
|
||||
assert(op_rdy);
|
||||
|
||||
if (result_vld && $past(result_vld) && !$past(op_kill))
|
||||
assert($stable({result_h, result_l}));
|
||||
|
||||
// Kill will halt an in-progress operation, but a new operation may be
|
||||
// asserted simultaneously with kill.
|
||||
if ($past(op_kill))
|
||||
assert(op_rdy == !$past(op_vld));
|
||||
|
||||
// We should be periodically ready (liveness property), unless new operations
|
||||
// are forced in immediately, simultaneous with a kill, in which case there
|
||||
// is no intermediate ready state.
|
||||
alive = op_rdy || (op_kill && op_vld);
|
||||
for (i = 1; i <= XLEN / MULDIV_UNROLL + 3; i = i + 1)
|
||||
alive = alive || $past(op_rdy || (op_kill && op_vld), i);
|
||||
assert(alive);
|
||||
end
|
||||
|
||||
`endif
|
||||
|
||||
endmodule
|
||||
|
||||
`ifndef YOSYS
|
||||
`default_nettype wire
|
||||
`endif
|
||||
@@ -0,0 +1,31 @@
|
||||
/*****************************************************************************\
|
||||
| Copyright (C) 2022 Luke Wren |
|
||||
| SPDX-License-Identifier: Apache-2.0 |
|
||||
\*****************************************************************************/
|
||||
|
||||
// req: one-hot bitmap
|
||||
// idx: index of the sole set bit in req
|
||||
|
||||
`default_nettype none
|
||||
|
||||
module hazard3_onehot_encode #(
|
||||
parameter W_REQ = 16,
|
||||
parameter W_GNT = $clog2(W_REQ) // do not modify
|
||||
) (
|
||||
input wire [W_REQ-1:0] req,
|
||||
output reg [W_GNT-1:0] gnt
|
||||
);
|
||||
|
||||
always @ (*) begin: encode
|
||||
reg [W_GNT:0] i;
|
||||
gnt = {W_GNT{1'b0}};
|
||||
for (i = 0; i < W_REQ; i = i + 1) begin
|
||||
gnt = gnt | ({W_GNT{req[i[W_GNT-1:0]]}} & i[W_GNT-1:0]);
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
`ifndef YOSYS
|
||||
`default_nettype wire
|
||||
`endif
|
||||
@@ -0,0 +1,33 @@
|
||||
/*****************************************************************************\
|
||||
| Copyright (C) 2022 Luke Wren |
|
||||
| SPDX-License-Identifier: Apache-2.0 |
|
||||
\*****************************************************************************/
|
||||
|
||||
// req: bitmap
|
||||
// idx: bitmap with all bits clear except the least- (HIGHEST_WINS=0) or
|
||||
// most- (HIGHEST_WINS=1) significant set bit in req.
|
||||
|
||||
`default_nettype none
|
||||
|
||||
module hazard3_onehot_priority #(
|
||||
parameter W_REQ = 16,
|
||||
parameter HIGHEST_WINS = 0
|
||||
) (
|
||||
input wire [W_REQ-1:0] req,
|
||||
output reg [W_REQ-1:0] gnt
|
||||
);
|
||||
|
||||
always @ (*) begin: select
|
||||
integer i;
|
||||
for (i = 0; i < W_REQ; i = i + 1) begin
|
||||
gnt[i] = req[i] && ~|(req & (
|
||||
HIGHEST_WINS ? ~({W_REQ{1'b1}} >> (W_REQ - 1 - i)) : ~({W_REQ{1'b1}} << i)
|
||||
));
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
`ifndef YOSYS
|
||||
`default_nettype wire
|
||||
`endif
|
||||
@@ -0,0 +1,77 @@
|
||||
/*****************************************************************************\
|
||||
| Copyright (C) 2022 Luke Wren |
|
||||
| SPDX-License-Identifier: Apache-2.0 |
|
||||
\*****************************************************************************/
|
||||
|
||||
// req: bitmap of requests
|
||||
// priority: packed array of dynamic priority level of each request
|
||||
// gnt: one-hot bitmap with the highest-priority request.
|
||||
|
||||
`default_nettype none
|
||||
|
||||
module hazard3_onehot_priority_dynamic #(
|
||||
parameter W_REQ = 8,
|
||||
parameter N_PRIORITIES = 2,
|
||||
parameter PRIORITY_HIGHEST_WINS = 1, // If 1, numerically highest level has greatest priority.
|
||||
// Otherwise, numerically lowest wins.
|
||||
parameter TIEBREAK_HIGHEST_WINS = 0, // If 1, highest-numbered request at the highest priority
|
||||
// level wins the tiebreak. Otherwise, lowest-numbered.
|
||||
// Do not modify:
|
||||
parameter W_PRIORITY = $clog2(N_PRIORITIES)
|
||||
) (
|
||||
input wire [W_REQ*W_PRIORITY-1:0] pri,
|
||||
input wire [W_REQ-1:0] req,
|
||||
output wire [W_REQ-1:0] gnt
|
||||
);
|
||||
|
||||
// 1. Stratify requests according to their level
|
||||
reg [W_REQ-1:0] req_stratified [0:N_PRIORITIES-1];
|
||||
reg [N_PRIORITIES-1:0] level_has_req;
|
||||
|
||||
always @ (*) begin: stratify
|
||||
reg signed [31:0] i, j;
|
||||
for (i = 0; i < N_PRIORITIES; i = i + 1) begin
|
||||
for (j = 0; j < W_REQ; j = j + 1) begin
|
||||
req_stratified[i][j] = req[j] &&
|
||||
pri[W_PRIORITY * j +: W_PRIORITY] == i[W_PRIORITY-1:0];
|
||||
end
|
||||
level_has_req[i] = |req_stratified[i];
|
||||
end
|
||||
end
|
||||
|
||||
// 2. Select the highest level with active requests
|
||||
wire [N_PRIORITIES-1:0] active_layer_sel;
|
||||
|
||||
hazard3_onehot_priority #(
|
||||
.W_REQ (N_PRIORITIES),
|
||||
.HIGHEST_WINS (PRIORITY_HIGHEST_WINS)
|
||||
) prisel_layer (
|
||||
.req (level_has_req),
|
||||
.gnt (active_layer_sel)
|
||||
);
|
||||
|
||||
// 3. Mask only those requests at this level
|
||||
reg [W_REQ-1:0] reqs_from_highest_layer;
|
||||
|
||||
always @ (*) begin: mux_reqs_by_layer
|
||||
integer i;
|
||||
reqs_from_highest_layer = {W_REQ{1'b0}};
|
||||
for (i = 0; i < N_PRIORITIES; i = i + 1)
|
||||
reqs_from_highest_layer = reqs_from_highest_layer |
|
||||
(req_stratified[i] & {W_REQ{active_layer_sel[i]}});
|
||||
end
|
||||
|
||||
// 4. Do a standard priority select on those requests as a tie break
|
||||
hazard3_onehot_priority #(
|
||||
.W_REQ (W_REQ),
|
||||
.HIGHEST_WINS (TIEBREAK_HIGHEST_WINS)
|
||||
) prisel_tiebreak (
|
||||
.req (reqs_from_highest_layer),
|
||||
.gnt (gnt)
|
||||
);
|
||||
|
||||
endmodule
|
||||
|
||||
`ifndef YOSYS
|
||||
`default_nettype wire
|
||||
`endif
|
||||
@@ -0,0 +1,41 @@
|
||||
/*****************************************************************************\
|
||||
| Copyright (C) 2021-2022 Luke Wren |
|
||||
| SPDX-License-Identifier: Apache-2.0 |
|
||||
\*****************************************************************************/
|
||||
|
||||
// req: bitmap
|
||||
// gnt: index of least set bit (HIGHEST_WINS=0) or most set bit (HIGHEST_WINS=1)
|
||||
|
||||
`default_nettype none
|
||||
|
||||
module hazard3_priority_encode #(
|
||||
parameter W_REQ = 16,
|
||||
parameter HIGHEST_WINS = 0,
|
||||
parameter W_GNT = $clog2(W_REQ) // do not modify
|
||||
) (
|
||||
input wire [W_REQ-1:0] req,
|
||||
output wire [W_GNT-1:0] gnt
|
||||
);
|
||||
|
||||
wire [W_REQ-1:0] gnt_onehot;
|
||||
|
||||
hazard3_onehot_priority #(
|
||||
.W_REQ (W_REQ),
|
||||
.HIGHEST_WINS (HIGHEST_WINS)
|
||||
) priority_u (
|
||||
.req (req),
|
||||
.gnt (gnt_onehot)
|
||||
);
|
||||
|
||||
hazard3_onehot_encode #(
|
||||
.W_REQ (W_REQ)
|
||||
) encode_u (
|
||||
.req (gnt_onehot),
|
||||
.gnt (gnt)
|
||||
);
|
||||
|
||||
endmodule
|
||||
|
||||
`ifndef YOSYS
|
||||
`default_nettype wire
|
||||
`endif
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*****************************************************************************\
|
||||
| Copyright (C) 2021-2022 Luke Wren |
|
||||
| SPDX-License-Identifier: Apache-2.0 |
|
||||
\*****************************************************************************/
|
||||
|
||||
// Implement the three shifts (left logical, right logical, right arithmetic)
|
||||
// using a single log-type barrel shifter. Around 240 LUTs for 32 bits.
|
||||
// (7 layers of 32 2-input muxes, some extra LUTs and LUT inputs used for arith)
|
||||
|
||||
`default_nettype none
|
||||
|
||||
module hazard3_shift_barrel #(
|
||||
`include "hazard3_config.vh"
|
||||
,
|
||||
`include "hazard3_width_const.vh"
|
||||
) (
|
||||
input wire [W_DATA-1:0] din,
|
||||
input wire [W_SHAMT-1:0] shamt,
|
||||
input wire right_nleft,
|
||||
input wire rotate,
|
||||
input wire arith,
|
||||
output reg [W_DATA-1:0] dout
|
||||
);
|
||||
|
||||
reg [W_DATA-1:0] din_rev;
|
||||
reg [W_DATA-1:0] shift_accum;
|
||||
reg sext; // haha
|
||||
|
||||
always @ (*) begin: shift
|
||||
integer i;
|
||||
|
||||
for (i = 0; i < W_DATA; i = i + 1)
|
||||
din_rev[i] = right_nleft ? din[W_DATA - 1 - i] : din[i];
|
||||
|
||||
sext = arith && din_rev[0];
|
||||
|
||||
shift_accum = din_rev;
|
||||
for (i = 0; i < W_SHAMT; i = i + 1) begin
|
||||
if (shamt[i]) begin
|
||||
shift_accum = (shift_accum << (1 << i)) |
|
||||
({W_DATA{sext}} & ~({W_DATA{1'b1}} << (1 << i))) |
|
||||
({W_DATA{rotate && |EXTENSION_ZBB}} & (shift_accum >> (W_DATA - (1 << i))));
|
||||
end
|
||||
end
|
||||
|
||||
for (i = 0; i < W_DATA; i = i + 1)
|
||||
dout[i] = right_nleft ? shift_accum[W_DATA - 1 - i] : shift_accum[i];
|
||||
end
|
||||
|
||||
`ifdef HAZARD3_ASSERTIONS
|
||||
always @ (*) begin
|
||||
if (right_nleft && arith && !rotate) begin: asr
|
||||
assert($signed(dout) == $signed(din) >>> $signed(shamt));
|
||||
end else if (right_nleft && !arith && !rotate) begin
|
||||
assert(dout == din >> shamt);
|
||||
end else if (!right_nleft && !arith && !rotate) begin
|
||||
assert(dout == din << shamt);
|
||||
end
|
||||
end
|
||||
`endif
|
||||
|
||||
endmodule
|
||||
|
||||
`ifndef YOSYS
|
||||
`default_nettype wire
|
||||
`endif
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Quick reference model for sequential unsigned multiply/divide/modulo
|
||||
|
||||
def div_step(w, accum, divisor):
|
||||
sub_tmp = accum - (divisor << (w - 1))
|
||||
underflow = sub_tmp < 0
|
||||
if not underflow:
|
||||
accum = sub_tmp
|
||||
accum = (accum << 1) | (not underflow)
|
||||
return accum
|
||||
|
||||
def divmod(w, dividend, divisor, debug=True):
|
||||
accum = dividend
|
||||
for i in range(w):
|
||||
accum_prev = accum
|
||||
accum = div_step(w, accum, divisor)
|
||||
if debug:
|
||||
print("Step {:02d}: accum {:0{}x} -> {:0{}x}".format(
|
||||
i, accum_prev, int(w / 2), accum, int(w / 2)))
|
||||
return (accum >> w, accum & ((1 << w) - 1))
|
||||
|
||||
def mul_step(w, accum, multiplicand):
|
||||
add_en = accum & 1
|
||||
accum = accum >> 1
|
||||
if add_en:
|
||||
accum += (multiplicand << (w - 1))
|
||||
return accum
|
||||
|
||||
def mul(w, multiplicand, multiplier, debug=True):
|
||||
accum = multiplier
|
||||
for i in range(w):
|
||||
accum_prev = accum
|
||||
accum = mul_step(w, accum, multiplicand)
|
||||
if debug:
|
||||
print("Step {:02d}: accum {:0{}x} -> {:0{}x}".format(
|
||||
i, accum_prev, int(w / 2), accum, int(w / 2)))
|
||||
return (accum >> w, accum & ((1 << w) - 1))
|
||||
|
||||
def divtest(w=4):
|
||||
for i in range(2 ** w):
|
||||
for j in range(1, 2 ** w):
|
||||
gatemod, gatediv = divmod(w, i, j, debug=False)
|
||||
goldmod, golddiv = (i % j, i // j)
|
||||
print("{:02d} % {:02d} = {:02d} (gold {:02d}); ./. = {:02d} (gold {:02d})"
|
||||
.format(i, j, gatemod, goldmod, gatediv, golddiv))
|
||||
assert(gatemod == goldmod)
|
||||
assert(gatediv == golddiv)
|
||||
|
||||
def multest(w=4):
|
||||
for i in range(2 ** w):
|
||||
for j in range(2 ** w):
|
||||
gateh, gatel = mul(w, i, j, debug=False)
|
||||
gold = i * j
|
||||
goldl, goldh = (gold & ((1 << w) - 1), gold >> w)
|
||||
print("{:02d} * {:02d} = ({:02d} (gold {:02d}), {:02d} (gold {:02d})"
|
||||
.format(i, j, gateh, goldh, gatel, goldl))
|
||||
assert(gatel == goldl)
|
||||
assert(gateh == goldh)
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Test division:")
|
||||
divtest()
|
||||
print("Test multiplication:")
|
||||
multest()
|
||||
Reference in New Issue
Block a user